免费注册 登录
»专业资料»IT/计算机»计算机软件及应用»Excel-VBA-从入门到精通必备.pdf
收起/展开

1.1.1 常规公式法 - Excel-VBA-从入门到精通必备

文档类型:pdf 上传时间:2018-08-27 文档页数:508页 文档大小:8.29 M 文档浏览:5478次 文档下载:2次 所需积分:0 学币 文档评分:3.0星

1.1.1 常规公式法 - Excel-VBA-从入门到精通必备内容摘要: Excel VBA 程序开发自学通2020-2-26第 1页 /共 508页为入门篇:VBA 优势、功能与概念第一章 从 Excel 插件认识 VBA简单的说,Excel VBA 是依附于 Excel 程序的一种自动化语言,它可以使常用的程序自动化,类似于 DOS(磁盘操作系统)中的批处理文件(后缀名“.bat”)。那么它有什么具体的功能?在工作中与常规操作方式相比,具有哪些优势?笔者试图通过一个简单却实用的插件来展现。本章要点: 从身份证号获取个人信息 在工作中如何发挥 Excel 插件的优势1.1从身份证号获取个人信息制作人事资料时,通常需要录入职员身份证号码,以及生日、年龄、性别等等。除身份证号码需要手工逐一录入以外,其它三项信息的录入有四种方法:手工录入、内置公式、自定义函数法、插件法。手工输入方式效率极差,且出错机率也最高,本节通过后三种方式来实现并比较,从而让读者对 VBA 之优势与用法得以初步认知。1.1.1常规公式法以图 1.1 数据为例,利用公式从身份证中提取生日、年龄、性别等信息,可以有多种方法。本例列举其中之一。图 1.1 根据身份证号提取职工年龄、生日与性别通过公式计算职工的年龄、出生日期与性别,步骤如下:(1)在单元格 C3 输入以下公式,用于计算年龄:=DATEDIF(DATE(MID(B3,7,4-(LEN(B3)=15)*2),MID(B3,11-(LEN(B3)=15)*2,2),MID(B3,13-(LEN(B3)=15)*2,2)),NOW(),"Y")(2)在单元格 D3 输入以下公式,用于计算出生日期:Excel VBA 程序开发自学通2020-2-26第 2页 /共 508页=TEXT(RIGHT(19&MID(B3,7,LEN(B3)/2-1),8),"#年##月##日")(3)在单元格 E3 输入以下公式,用于计算性别:=IF(ISODD(MID(B3,15,3)),"男","女")注意:在 Excel 2003 中,ISODD 函数默认状态下无法使用,需要加载“分析工具库”才可以正常使用,为了使公式通用,通常改用 MOD 函数。即公式改为:=IF(MOD(MID(B3,15,3),2),"男","女")(4)选择 C3:E3 区域,将公式向下填充即完成身份证信息提取。效果如下:图 1.2 公式法获取身份证信息点评:相对于手工输入法,利用公式从身份证号码获取个人信息有着效率更高、错误率更低之优点,人员越多时越能体现出其高效优势。本例文件参见光盘:..\ 第一章\提取身份证信息.xlsm1.1.2自定义函数法自定义函数是指利用 VBA 编写的外置函数。在本例的随书光盘中已经录入了相关的 VBA 代码,可以随时调用。对于代码的含义和录入方式在后面的章节后有详细介绍,本章仅通过具体应用了解其用法与优势。具体操作步骤如下:(1)进入“自定义函数法”工作表;(2)在 C3:E3 区域分别输入以下三个公式,用于计算年龄、出生日期和性别:=SFZ(B3,"NL")=SFZ(B3,"SR")=SFZ(B3)或者=SFZ(B3,"XB")(3)选择 C3:E3 单元格,将公式向下填充,结果见图 1.3 所示。Excel VBA 程序开发自学通2020-2-26第 3页 /共 508页图 1.3 自定义函数法获取身份证信息本例中的函数 SFZ 即身份证函数,用于从身份证号码中获取年龄、生日与性别等信息。它不属于 Excel 内置函数,需要利用 VBA 编写代码才可以使用。读者可以从随书光盘中获取该完整代码。SFZ 函数有两个参数,第一参数为单元格引用,第二参数为信息描述,即用于指定需要获取身份证中哪一部分信息。当它为“NL”(不区分大小写)时,获取年龄;当它为“SR”时,获取生日,当它为“XB”或者省略第二参数时,获取性别。点评:相对于内置函数法/公式法,自定义函数法是借用 VBA 编写的外置函数完成,它的优势在于公式简短,且容易理解。任何不熟悉函数与 VBA 者皆可一分钟内学会操作并理解其公式含义。1.1.3插件法插件法是指借用 Excel 插件操作工作表,该插件不隶属于当前工作簿,但却可以实现与当前工作簿交互的功能,批量、迅速完成身份证信息提取工作。操作步骤如下:(1)关闭 Excel 程序的前提下,将随书光盘中的插件(位置:..\第一章\批量获取身份证信息.xlam)复制到以下自启动文件夹中即安装完成:C:\Program Files\Microsoft Office\Office12\XLSTART注意:如果您的 OFFICE 没有装在 C 盘,那么上面的磁盘号需要根据实际情况做修改;如果您使用 OFFICE 2003,则将其中“Office12”修改为“Office11”。(2)打开光盘文件“提取身份证信息.xlsm”,进入“插件法”工作表;(3)选择单元格区域 B3:B6,单击右键,从右键中选择【批量获取身份证信息】菜单,程序将弹出一个对话框“确定计算区域”。该对话框中默认显示当前选区地址,如果需要修改地址,可以输入新的地址,也可以用鼠标在工作表中选择身份证存放区域,该区域的地址会自动产生在对话框中。见图 1.4 所示;(4)单击“确定”按钮,程序在瞬间就会从选区的所有身份证中提取年龄、生日和性别等信息。Excel VBA 程序开发自学通2020-2-26第 4页 /共 508页图 1.4 插件法批量获取身份证信息点评:插件法从身份证号码中获取信息的优点是速度快,通用性好。相对于内置函数法,它在操作上更简单,不需要任何函数知识,不需要输入长长的公式,只点几次鼠标即可;相对于自定义函数,它的优点是通用性好,在任何工作表、任何工作簿皆可使用本工具。而前一方法之自定义函数非插件方式存在,只能在当前工作簿中使用。1.1.4浅谈 VBA 优势前面三个案例中我们可以看出,Excel 具有强大的计算功能,但常规方式对于某些大型数据运算显得比较繁琐。用户需要学习复杂的函数知识,设置长长的公式才可以解决某些运算。而 VBA 可以使公式简化、易懂,甚至根本不需要公式,一个字母不用录入即可完成一些专业性较强的计算。具体说来,相对于 Excel 自带的功能,VBA 或者说 VBA 开发的插件具有以下优势: 批量地对操作对象进行数据处理以前一节插件法完成身份证信息进行例证,它可以瞬间完成多个单元格数据的运算,甚至多个工作表中存放的身份证号码也可瞬间完成信息提取。较传统的逐一处理方式在效率上有大幅提升。 多任务一键完成多任务是指对同一个对象需要进行多个操作,例如前一节是从身份证号码中获取三类信息,VBA 可以单击一个按钮后瞬间完成,完全感觉不到它在分三步逐一完成任务。这是高效办公地最佳体现。 将复杂的任务简化Excel 是很多很多小工具的综合体。这些工具可以嵌套运用,完成更强大的数据处理。但当嵌套过多时,就需要用户要较深的功底才能操纵或者理解。另一方面,对于某些特殊行业的工作、任务,也要经过很复杂的操作才可以完成,而对于某些只需要应用不需要深入研究、理解的普通办公文员们来说是一个技能考验。而通过 VBA进行二次开发可以将复杂的任务变得更简单。简单是指理解和操作上同时简化。就像 1.1.3 节中通过右键菜单提取身份证号码三类信息一样,不需要用户去录入长长的公式,以及理解信息是如何提取出来的,单击菜单即可完成。再如企业中生成工资条,10000 个人的资数用手工操作需要处理 10000*N 次,而利用 Excel 插件可以Excel VBA 程序开发自学通2020-2-26第 5页 /共 508页单击按钮完瞬间成。 将工作表数据提升安全性利用 VBA 代码可以对数据进入多层保护,在某些特殊需求下,VBA 可以保护数据让普通用户无法胡乱修改,或者不小心破坏数据及数组结构。 提升数据准确性准确性体现在数据录入和数据运算两方面。首先,通地 VBA 对输入的数据进入限制,可以防止用户意外录入不规范字符。如数字中有两个小数点,或者录入数值时不小心录入了标点或者字母,造成无法计算或者漏算。其次,在数据运算时,人工设置大量公式,或者每天在不同地方重复录入同一个公式。在大量地操作中难以避免不产生一次错误。而利用 VBA 可以让工作简化,工作量越小,出错的机率一定越小;同时,在大量重复性工作中 VBA 可以确保不产生错误。 完成 Excel 本身无法完成的任务弹出提示、警告对话框、行程安排与预告,或者到磁盘中查找需要的数据、修改注册表等等,Excel 常规方式是不可能完成的。如果需要类似功能,VBA 完全可以胜任。 开发专业程序利用 VBA 还可以开发一些专业型的程序,如报表汇总软件、进销存管理系统、人事管理系统等等,可以将界面设置成其它任何软件的显示方式媲美专业的程序软件1.2 插件特点及其如何发挥插件的优势在前一节中,通过一个身份证信息获取的插件认识了 Excel 插件,那么在工作中应如何发挥 Excel 插件的优势呢?1.2.1Excel 插件的特点Excel 插件是利用 VBA 程序开发的外置工具,通常是 xla、xlam 格式或者 dll 格式。其中 xla 和 xlam 插件直接用 Excel 就可以开发,而 dll 插件通常采用 VB 或者 C++来编写。不管何种软件开发的插件,它都需要在外观和功能两方面具有某些特征,以方便用户调用。1.外观特征 有若干个菜单或者工具按钮在插件封装后,调用其代码有两种方式:用代码调用,用菜单或者工具栏按钮,显然菜单更方便。用户通过菜单单击即可完成相对于常规方式较复杂的操作或者运算。 利用窗体实现与工作表数据交互在弹出的窗体中可以调用工作表的数据,也可以将窗体中录入的数据导到工作表。而在窗体中录入数据时,相对于工作表中录入数据,可更好地控制。例如某个文字框中可以指定只能录入数字,而另一个文字框可以指定只能入日期。也可以设定录入某项目后自动跳转到指定目标位置,而不用手动去移动光标插入点。甚至可以在录入时核对是否与工作表中数据是否重复等等……Excel VBA 程序开发自学通2020-2-26第 6页 /共 508页 有一个帮助界面对于开发者来说,不管自己开发的工具如何简单,都有必要向用户说明其功能和操作方式。所以在工具中通常加入一个窗体,进行文字说明或者动画演示。特别是工具没有提供菜单、而是通过函数调用或者快捷键调用时,更需要一个说明窗体。 对函数做参数说明对于函数类插件,必须对每个函数的参数进入详细说明,让用户插入函数时可以清晰明了地看到每个函数中每个参数的功能与使用方式。2.功能特征Excel 插件中的代码和普通宏程序的代码在编写上具有一些差异,这是它们的设计目的不同造成的。其中宏代码通常用于解决某个具体的问题,它可能限用一次,也可能需要反复调用。但都只为解决自己的某个具体问题而录制。而开发 Excel 插件则通常是开发者开发后,给其他的终端用户使用。用户不确定,需要操作的区域对象不确定。所以插件有不同的需求,它需要具有以下特征: 没有具体的区域地址由于开发插件通常是给其他的终端用户使用,所以不能指定数据区域地址,而是提供一个自由选择目标区域的选择对话框,或者利用代码计算目标工作表中的待计算区域。这是和录制宏最大的差异。 不使用具体的工作表名或者工作簿名原理与前一条一致。 必须有通于菜单或者窗体供用户调用命令,而不是在工作表中建立按钮来调用命令。dll 格式的插件不存在工作表,而 xla 和 xlam 格式插件的工作表是隐藏状态,工作表不可能在用户的界面呈现出来,所以必须建立一个通用的菜单栏,使其在打开任意工作簿都会显示出来供用户操作。如果使设置了快捷键,那么是可以不用菜单或工具栏的,界面将会更简洁。 尽可能提供自定义选项插件的针对性不强,即它需要有广泛性。插件通常不是为某一个固定用户开发,或者需要处理的数据并非永远一致,那么在不同用户使用同一功能时,需要有自定义其参数或者选项的空间,工具才能有更好的通用性。例如设计一个工资条制作插件,那么工资条的表头行数就有必要让用户选择,而非强制一行或者两行。这和编写一个解决临时性问题的编程思路不同。 具有多版本适应能力目前办公用户使用的 Excel 版本差异很大,有 Excel 2000、Excel XP、Excel 2003,也有 Excel 2007。开发者不会假定用户都用某个版本的 Excel,而是通过代码判断当前用户的版本号,然后调用不同的代码,以适应当前版本,否则某些功能可以无法使用。 防错机制自用型宏程序通常不用防错,因为用户和开发者是同一人。而插件则必须有完善的防错机制,预先设置了遇到某种错误该如何反应的措施,避免破坏用户数据,或者进入死循环,消耗尽计算机的内存资源。Excel VBA 程序开发自学通1.2.22020-2-26第 7页 /共 508页Excel 插件的优势与限制在工作中使用插件,可以使用工作更轻松,运算更快速、准确。当然前提是插件的代码编写足够优秀,不仅具有很强的通用性,还要有完善的防错机制,以及灵活的自定义选项。那么工作中使用优秀的插件进行工作具有哪些优势呢? 简化操作:类似于 bat 批处理文件,可以一键执行多个任务 强化功能:对 Excel 内置功能无法完成的一些任务,借用 VBA 代码可以实现 美化界面:VBA 用以调用 Flash 动画,也可以播放 Gif 动画,还可以直接对单元格字符产生滚动效果。对于喜欢装点的用户,借用 VBA 可对工作表进行很好地修饰 固化格式:VBA 可以对录入的数据进入检测,阻止输入不规范的数据;也可以禁止新增、删除工作表,或者禁止缩放窗口,从而促使多用户文件能确保格式一致,便于汇总虽然插件在工作中有以上优势,但它在某些方面也具有一些限制: 通用性方面:开发插件通常是个人行为,而非 Office 软件一样由一个大公司主持。所以其通用性很可能不是很好,开发者测试的次数少以及测试条件不足等等,导致工具具有某些隐含的缺陷 防错方面:程序员不一定是终端用户,甚至可能从来没有成为办公用户,而是直接学习插件开发。那么在程序编写时就可能思维受限,无法对可能出现的所有错误进行防范 移植方面:插件属于外置工具,它的所有功能都需要安装才能使用。所以如果利用插件设计的表格有可能传用客户后无法正常开启,或者开启后无法正常显示。最好的解决方法是将插件让客户端也安装一次 独立方面:Excel 的 VBA 是依附于 Excel 主体程序的附属程序,它可以开发强化 Excel 功能的程序,但不能开发脱离 Excel 而单独存在的软件。如果需要开发全新而专业的应用程序,VBA 并非理想的程序1.2.3如何发挥插件的优势可以确定的是,善用插件可以提升工作效率。但是插件也不可滥用,否则享用优势的同时,也会产生一些后患。首先,需要明白插件相对于 Excel 的功能属于外置工具,它需要安装后才能使用。如果读者的文件非自用型,需要与他人共享、阅读,那么需要连插件一起共享;其次,如果是简单的功能,尽量使用内置功能,少用插件。插件适用于处理复杂的或者 Excel 内置功能无法完成的工作;宏有一个通用 BUG,即使用宏代码后,内置撤消功能将禁用。为了让用户减少损失,针对某些会更新数据、修改(破坏)原有格式的工具,一定要提供一个恢复原状的程序。例如有制作工资的工具,就搭配一个删除工资条的工具。最后,尽量将插件在同部门共享。即一个办公室为单位或者一个企业为单位,让整个单位都拥有相同的插件,才能更好地发挥插件优势。Excel VBA 程序开发自学通1.2.42020-2-26第 8页 /共 508页开发 Excel 插件的条件针对插件的开发者,他/她需要什么条件呢?现罗列如下: 熟练撑握 VBA 技术这是首要条件。必须对大部分常用对象及其属性熟练地掌握。且需要了解数据处理的常用方式,并从多种处理方式中找出最高效且通用的方式。如果在某些特殊情况下,程序的通用性与执行效率只能选择其一时,通用性优先于执行速度。 具有一定的报表操作经验仅学习 VBA 是可以熟练掌握 VBA 知识的,但是仅掌握 VBA 知识却不可能成为优秀的程序员。例如开发财务人员用的插件,那么需要懂得一些财务知识,不需要精通,但一定要对财务知识有所了解或者有财务报表的制作经验,才可能开发出适合于财务人员的插件。 美化常识这里的美化并不一定是漂亮的外观,而是要使自己开发的程序界面具有协调性、统一性,还需要了解普通用户的操作习惯,根据习惯设计人性化或者操作更便利的界面。当然,在不影响效率的前提下,将窗体设计得更美观,也是具有现实意义的。 熟悉不同版本的 Excel 间的差异终端用户们有可能使用多个版本的 Excel,那么开发者也需要了解不同版本间的差异。例如 Excel 2003 中 Application 有一个属性 FileSearch,用于在磁盘中查找文件,而 Excel 2007 取消了该属性,那么开发插件时就应尽量避免使用该属性,借用其它方法的代替。否则将产生兼容性问题,以致程序产生 BUG。 具有较强的耐心编写程序是一个与字母相处的过程。对于大中型程序,可能长时间对着一堆字母或者数字,这需要有一定的忍耐力。甚至在程序开发完成后,仍然需要耐心对程序进行多角度、多版本的测试,以提升程序的通用性和纠错性。1.2.5本书架构本书除 VBA 基本理论外,偏重于讲解插件开发的原理、思路与方法以及如何提升程序速度。在以后的章节中,主要从按以下方式进行编排:(1) VBA 历史与功能、安全性等等周边知识简要介绍(2) 认识 VBE 编辑器并对其它进行优化设置(3) 学习 VBA 中常用对象及属性、方法、事件(4) VBA 代码如何提升执行速度(5) 掌握 VBA 高级应用,包括窗体的认识,及磁盘、目录与文件操作(6) 开发 VBE 环境下的插件(7) 学习利用 VB 开发专业性的 COM 加载宏插件(8) 最后利用前面章节的知识开发一个大型 Excel 插件。从该插件的开发思路和过程让读者了解插件开发的常规流程及注意事项本书以插件开发为重心,但对于 VBA 中常用知识,不一定与插件开发相关的知识但工作中较常用的功能也会进行详解,或者进行实例演示。除插件开发外,程序的提升和防错在本书的多次强调的重点。从第二章开始,让读者学习、掌握 VBA 理论知识,为插件开发提供基础。Excel VBA 程序开发自学通2020-2-26第 9页 /共 508页第二章 VBA 概述VBA 是 VB(VISUAL BASIC) 的一个子集,是一种附属于 Excel 的程序软件。在学习 VBA 的语法之前有必要对其发展史、功能、特点等等方面进行了解。本章要点:Excel VBA 程序开发自学通2020-2-26第 10页 /共 508页VBA 的发展史与优缺点VBA 能做什么VBA 的安全性使用 VBA 帮助2.1VBA 的发展史与优缺点VBA 语言作 VB 家族成员,起步很早。发展至今已拥有非常广大的用户群,在日常办公中有着举足轻重的作用。2.1.1宏与 VBAExcel 早在 1985 年就首次在 Machintosh 上出现,1987 年 Excel 开始引进到 Windows环境中。当时 Lotus 1-2-3 是计算机历史上最成功的软件系统之一,但它仅支持一些极其简单的宏,而 Excel 软件从 Excel 4 开始,可以使用相对复杂的 xlm 宏,完成更复杂的工作,慢慢的将 Lotus 1-2-3 挤出电子表格行业,迅速占领了市场。当 Excel 5 中正式推出 VBA(Visual Basic for Applications)作为通用的宏语言来为 Office 应用程序编写代码后,Excel 已完全征服了制表用户。可见宏语言在表格软件中影响之深远。宏的英文名为 Macro,是自动执行某种操作的命令集合。它包括两个过程,即 Excel4 或者称为 xlm 的宏语言和 Excel 5 中的 VBA 宏。Excel 4 的宏由宏表函数构成,由录入在宏表中的函数来控制程序的执行。至 1993 年发布的 Excel 5 中,微软开始推广VBA 做为宏语言,并同时引进 VBA 编辑器,即 VBE(Visual Basic Edirtor)。用户可以通过录制宏来产生代码,代码储存在 VBE 环境的代码模块中,利用 Alt+F8 可以反复调用录制的宏。VBA 是目前 OFFICE 系列通用的一种程序语言,它支持录制、执行、单步执行、调试等等操作,可以使用户从繁重的制表任务中解脱出来。VBA 是一种面向对象的程序语言,由一种所见即所得的方式编写代码,这使它在学习和使用方面都相比其它语言更简单。事实上,几乎所有 VBA 程序员都由录制宏开始学习 VBA,这是一个VBA 速成的捷径。甚至 VBA 高手们仍然对录制宏乐此不倦,因为它可以完成 VBA程序的大部分代码,程序员仅需在录制的宏代码中稍加修改即可成为最后的合格程序;另一个最重要的因素是录制宏可以为程序员提供词典的作用,即忘记了某个对象单词,或者完全不明白某个属性的语法时,利用录制宏可以产生对应的代码,用户复制即可使用。2.1.2VBA 历史与版本VBA 的前身是 xlm 宏语言,鉴于 xlm 宏功能有限,至今已经用 VBA 完全替代了xlm 宏。但是为了体现兼容性,所有版本的 VBA 中皆可以调用以前的部分宏表函数。例如 Excel 2007 的 application 对象仍然保留了以下宏表相关的一个方法和两个属性,通过它们可以执行早期宏表所有函数: Application.ExecuteExcel4Macro Application.Excel4MacroSheetsExcel VBA 程序开发自学通2020-2-26第 11页 /共 508页 Application.Excel4IntlMacroSheets在抛弃早期宏语语言后,VBA 从 1993 年开始逐步在很多软件中出现,除 OFFICE办公软件外,Cad、Coreldraw 等等软件也支持 VBA。目前 VBA 的最高版本是 6.05。但需要申明的是,VBA 版本并非与与主体程序的版本对应升级,即 Excel 的多个版本有可能使用同一版本的 VBA。如 OFFICE 2003 和 OFFICE 2007 都使用 6.05 版的 VBA。检测当前 OFFICE 中 VBA 版本可以使用以下代码:Sub 获取 VBA 版本号()MsgBox Application.VBE.VersionEnd Sub不同版本的 VBA 带有不同的函数,编程时需要根据 VBA 的版本调整体码,使之尽量通用。但在 Excel 中编写 VBA 程序时,Excel 的版本号显得更为重要。因为不同的 Excel 版本有不同的对象和方法,而且差异较大。在本书的附录中有 Excel 2007 与早期版在 VBA 方面的差异,做为插件开发者有必要进行全面了解。2.1.3VBA 优、缺点VBA 做为 OFFICE 办公套件的二次开发语言,它是一个很优秀的程序语言,从国内外 OFFICE 论坛中 VBA 相关的发贴量可以知道 VBA 用户群有多大,这也反证了VBA 在工作中应用之广泛性。总体来说,VBA 语言具有以下优点: 可以录制早期的磁盘操作系统 DOS 不支持录制,虽然它是一门很简单的语言,但要让大多数用户学好 DOS 仍然是一件难事。它的每个命令,每个字母都面要手工录入,所有命令都需在大脑记忆。而 VBA 采用录制方式可以产生完整的代码,程序稍加优化即可取得最佳程序,摆脱死记代码的困扰。 所见即所得Excel VBA 有窗体及工作簿、工作表等等对象,可以直接拖动产生对象,不需要编写创建对象的代码。而且可以调整为一边操作工作表数据或者图形对象,一边查看代码变化,即录制宏时同时查看工作簿窗口和代码窗口。 调用现成对象VB 或者 C++开发程序时需要自己设计窗体、对象,而 Excel 中有现成的工作簿对象、工作表对象、窗口对象、图形对象等等,开发者仅需对这些对象或者数据进行操作即可,不需要开发一个报表程序及各对数据存放介质。这也是 VBA 简单易学的原因之一。 应用广泛目前 Excel、Word、Access、PowerPoint、FrontPage、Visio、Project、Outlook、AutoCAD、CorelDraw 等等程序都支持 VBA。而各程序间的代码可以相互移植,然后对代码中的引用对象稍加修改即可。 交流方便这里说了交流是指 VBA 用户与用户之间的交流。国内、国外都有很多大型的 VBAOFFICE VBA相关论坛,可以通过论坛交流心得、学习他人的编程思路,以及在线提问。方面的论坛远比 C++与.net 等等语言的相关论坛更多。相对于 Excel 内置功能,VBA 也有它自己的缺点: 学习周期长Excel VBA 程序开发自学通2020-2-26第 12页 /共 508页学习 VBA 的时间至少两个月,而数据透视表、函数、图表等等其它内置功能则相对更快。 专业词汇多VBA 中有几百个对象,每个对象有多个属性以及方法,虽然不需要死记硬背所有对象名称和属性,但仍然需要花很多精力来理解、消化。 普及范围小目前 VBA 用户群在一天天扩大,但相对于 Excel 的内置功能如公式、图表等等,仍然有待进一步提升普及率。在普及不够的情况下,程序员的插件需要做更完善的帮助系统,也需要更多的时间来测试,使未接触 VBA 的用户能更快地掌握其技巧。2.2 VBA 能做什么VBA 是一门程序语言,工作中 VBA 的常见用途是什么?本节进行讨论。2.2.1VBA 用途可以肯定地说,VBA 可以完成 Excel 常规功能可以完成的任何功能。但是事实上不可能有人用 VBA 来处理所有任务,而是有选择性、有针对性地使用 VBA。概括地说,VBA 主要用在以下几方面: 处理大型运算Excel 内置的函数嵌套也可以完成很多大型的数据运算,然而很多易失性函数会造成 Excel 程序启动缓慢,特别是数组公式。用 VBA 来处理数据运算则可以解决这个问题。 工作簿/工作表折分/合并对于工作簿/工作表按条件拆分成多个工作表之任务,手工完成效率极度的低,VBA 则可以轻松完成,单击鼠标即可。也有部分企业需要每月汇总下属分公司的报表,多报表的汇总人工操作显然是事倍功半,而 VBA 插件则可一劳永逸。 处理重复性任务针对某些每天都需要进行且完全复重不变化的任务,利用 VBA 仅需要第一次手工操作、编写代码,后续的所有任务都全自动执行。它的优势在仅在于速度快,还更准确。人工操作的步骤越多,出错的机率一定相应的越大。 简化内置公式以第一章的公式为例,以下两个公式都可以从身份证号码获取年龄:=DATEDIF(DATE(MID(B3,7,4-(LEN(B3)=15)*2),MID(B3,11-(LEN(B3)=15)*2,2),MID(B3,13-(LEN(B3)=15)*2,2)),NOW(),"Y")=SFZ(B3,"NL")很显然,第二个公式输入效率高,且更易让用户理解。其第一参数为引用的身份证号,第二参数“NL”表示年龄。 定制程序界面对于某些喜好个性化的用户来说,Excel 是支持全面定制的程序。利用 VBA 可以将 Excel 界面定制成更具有个性化的程序。类似于 QQ 换肤、播放器换肤等等,Excel也可以通过 VBA 使用程序标题、状态栏、菜单个性化,例如产生滚动字幕,例如将Excel VBA 程序开发自学通2020-2-26第 13页 /共 508页个人照片、邮箱地址加到菜单栏等等。图 2.1 定制个性界面 开发受保护的专业程序网络上有很多 Excel 版的人事系统、学校成绩管理系统、考试座次安排系统等等,利用 VBA 可以编写很专业的程序,且能对其进行保护,以确保开发者的利益。即使是纯公式设计的报表,有时也需要借用 VBA 来设计程序注册功能或者登录界面等等。2.2.2VBA 主要用户根据 VBA 的特点,使用 VBA 主要有两类对象。其中最主要的是开发者就是终端用户,即编写代码给自己用的的业余程序员。而且 VBA 也是所有程序中拥业余程序员最多的一类程序。利用 VBA 解决一个临时问题,或者处理某一个具体的重复性任务,是大家使用 VBA 最多的原因。只有少数用户不为解决当前问题而基于兴趣编写一些通用型插件,可以解决很多很多类似问题。注意:普通宏程序和插件最主要的一个区别是:编写普通宏程序只为针对当前遇到的一个具体问题而编写,当前问题解决后,该程序也不再有存在的价值;而编写插件则通过针对具有大从性质的问题,例如工资条设计,很多企业、事业单位都需要,那么它的生存周期是很长的,用户也是不固定的,在编写时也就会产生更多的自定义选项留给终端用户。所以编写插件和自编自用型宏程序在困难度上有较大区别VBA 用户通常用于两个基本条件:其一为工作中某些任务利用常规方式处理太繁琐,需要 VBA 来简化工作;其二为因 VBA 兴而趣研究。笔者一直坚持的一种观点是:对 VBA 保持持续的兴趣是学好 VBA 很重要的条件。另外一类 VBA 用户即为专业的 VBA 程序员,专为别人定制程序。这类用户除了需要熟悉 VBA 语法外,还需要对 Excel 各项操作有较多的经验。也有部分人员本身就是某个行业的资深主管、兼职程序员。例如一个精通 VBA 的财务主管,他/她开发的财务类 VBA 程序一定比只精通 VBA 不懂财务的专业程序员开发的程序更专业,或者说更具有易用性。Excel VBA 程序开发自学通2020-2-26第 14页 /共 508页2.3 VBA 的安全性上世纪 90 年代宏病毒泛滥,使人们对宏以及宏的安全性都有所了解。但事实上很多用户也就通过宏毒病的传播了解宏的部分特点,这自然是很片面的。2.3.1VBA 安全性事物都有双面性,程序语言犹其如此。程序的功能越强,那么同时意味着用它做破坏也可以具有更大的破坏力。VBA 依附于 Excel 程序,但它做为病毒传播时,可以破坏的对象却不局限于 Excel 程序,磁盘中所有文件皆可以任意修改。正因如此,学习 VBA 时,有必要掌握好这把双刃剑。在默认状态下,Excel 2003 和 2007 都是禁用宏的,以确保用户数据不会因潜在的宏病毒而受破坏。但是同时也带来另一个问题——正常的 VBA 程序无法执行。所以通常有三种做法:不用 VBA 程序的用户,彻底禁用宏,杜绝宏病毒蔓延。常用 VBA 程序,包括自己开发和别人开发程序的用户,可以将宏的安全性稍加提高,即遇到宏时提示用户,当用户确定代码安全时再执行。第三类自编自用型用户或者完全信任宏代码开发者的用户,则可以将宏的默认设置修改为无限制。即允许任何宏执行,从而提升工作的效率。笔者属于第三类,永远允许所有宏自动执行。2.3.2了解安全性对话框Excel 2007 有两个安全性对话框。一个是打开带有宏代码时提示用户的“安全选项”对话框,见图 2.2 所示;另一个是位于 Excel 选项中的“信任中心”对话框,见图 2.3 所示。1.安全选项当启动一个具有宏代码的工作簿或者启动一个 COM 加载宏时,默认状态下在Excel 编辑栏上方会弹出一个“安全警告 部分活动内容已被禁用”的提示框(根据加载宏的类型不同,显示的文字也会有所差异)。当用户单击“选项”按钮后再次弹出一个“安全选项”窗口,在窗口中将罗列出所有被阻止的加载项。本例中有两个,包括一个 xla 格式的加载宏,和一个 dll 格式的 COM 加载项。如果确认需要在工作簿使用某个加载项,或者信任该加载项中的代码,则选择“启用此内容”,然后单击“确定”按钮,安全警告对话框消失,而相关加载项所携带的代码也可以任意调用了。Excel VBA 程序开发自学通2020-2-26第 15页 /共 508页图 2.2 安全选项图 2.3Excel 信任中心2.信任中心前面所说的每次在开启工作簿时启用自己信任的插件或者宏工作簿,虽然确保了安全,事实上效率不高,操作上较繁琐。而设置信任中心可以一劳永逸的解决上述问Excel VBA 程序开发自学通2020-2-26第 16页 /共 508页题。进入信任中心步骤如下:(1)单击左上角的圆形 OFFICE 按钮;(2)选择“Excel 选项”按钮打开 Excel 选项对话框;(3)单击左边的“信任中心”按钮即可显示“信任中心”对话框。见图 2.3 所示。Excel 选项的信任中心主要用于管理宏的安全性问题,它包括“受信任的发布者”、“受信任位置”、“加载项”、“ActiveX 设置”、“宏设置”、“消息栏”、“外部内容”和“个人信息选项”8 个选项,分别具有以下作用:受信任的发布者:罗列出本系统中所有数字签名证书。证书是文档中电子的、基于加密的安全验证戳。此签名确认该宏或文档来自签发者且没有被篡改,表明您认为该数据库是安全的并且其内容是可信的。这可以帮助数据库的用户确定是否信任该数据库及其内容。受信任位置:表示该位置下存放的代码是受信任的,不受宏的安全性设置的影响,任何情况下都会执行其代码。在本对话框中罗列了 Excel 默认设置下的几个位置。用户也可以手工添加或者添新的受信任位置。通常可以将自己编写的代码所存放的目录设置为受信任位置,以避免每次手工启用宏。加载项:添加或者删除加载项,包括 xla、xalm、dll 和 xll 格式的加载项。在本对话框中可以手工安装以及删除所有格式的插件。而笔者的习惯是 xla 格式或者 xlam格式的插件直接存到 Excel 自启动文件夹中,免除安装。对于 COM 加载项,即 dll格式的插件则可以使用本对话框中手工安装。获取自动启动的路径可以使用以下代码:Sub 自启动路径()MsgBox Application.StartupPathEnd Sub以上代码获取的是用户级自启动路径,即只对当前用户发生作用。如果需在任何用户打开 Excel 都可以启动插件中的宏,那么用可以将用以下路径:C:\Program Files\Microsoft Office\Office12\XLSTART其中 Office12 表示 Office 2007 的路径,如果用户安装的是 Office 2003,则修改为 Office11;另外“C:”也需要根据皮实际情况修改,例如 Office 安装在 D 盘则用“D:”。ActiveX 设置:控制 ActiveX 控件的启动方式,可以让禁用 ActiveX 控件且不发出通知,达到时最高的安全性,也可以禁后通用户选择,还可默认允许执行,以提高效率。在此处,效率和安全性是相冲突的。宏设置:宏设置类似于 ActiveX 设置,它是对携带宏代码的工作簿进行安全限制。同样包括与 ActiveX 设置相似的选项。不过最下边的“信任对 VBA 工程对象模型的访问”则不是控制宏的运行,而是用于控制代码对 VBE 环境的操作。默认状态下是禁用代码操作 VBE 环境中任何组件的,打勾后才允许读取或者修改 VBE 的任何组件,例如在 VBE 窗口中新建一个菜单,或者删除 VBA 代码模块。消息栏:控制 Excel 是否弹出消息栏,即阻止宏运行时是否通知用户。默认状态是要发出通知。外部内容:所谓外部内容指工作表中的公式引用发其它工作簿,包括加载宏中的数据。本选项决定是否禁止引用以及是否弹出提示。在确保数据安全的前提下尽量选择数据链接和自动更新,以提升效率。个人信息选项:本选项包括几个与网络相关的信息,在网络不可用的情况下设置无效。而网络畅通的情况下数据引用也比较慢,建议不再启用所有选项。Excel VBA 程序开发自学通2.3.32020-2-26第 17页 /共 508页让自己的 VBA 程序畅通无阻根据前面的描述,Excel 默认状态下是禁止宏代码和 ActiveX 控件运行的。而对于自己编写的完全可以信任的代码也产生了阻碍作用,这促使用户需要对其进行设置,让代码默认即可执行。让自己的 VBA 程序畅通无阻有两种方式,签署数字证书和设置受信任位置。从操作程序比较,设置受信任位置是最快速、最方便的办法。下面以安装“D:\工具箱\批量获取身份证信息.xla”为例演示如何让程序自启动而不被 Excel 阻止。(1)在 D 盘新建文件夹,命名为“工具箱”;(2)将“批量获取身份证信息.xlam”插件复制到“工具箱”文件夹中;(3)单击【Office 按钮】\【Excel 选项】\【信任中心设置】\【受信任位置】\【添加新位置】打开“Macrosoft Office 受信任位置”对话框,该对话框中默认显示 Office临时文件夹路径;(4)在“Macrosoft Office 受信任位置”对话框的“路径”文字框中输入“D:\工具箱\”,并勾选“同时信任此位置的子文件夹”,在“说明”文字框中输入说“我的信任位置”,见图 2.4 所示:图 2.4 设置受信任位置对话框(5)单击“确定”按钮后返回“受信任位置”,在列表中将会显示新增加的地址“D:\工具箱”;(6)单击“确定”按钮后返回“Excel 选项”对话框,单击左边的“加载项”按钮,再在底部的“管理”下拉列表中选择“Excel 加载项”,单击“转到”按钮打开“加载宏对话框”。该对话框中罗列了所有已安装的内置加载宏和外置加载宏,见图 2.5所示;(7)单击“浏览”按钮,从对话框中选择“D:\工具箱”中的“批量获取身份证信息.xla”,然后返回加载宏对话框,可以发现插件列表中已经新增了一项:“批量获取身份证信息”,见图 2.6 所示:Excel VBA 程序开发自学通2020-2-26图 2.5 加载宏对话框第 18页 /共 508页图 2.6 加载 xla 格式的插件(8)经过以上设置后,重启 Excel,可以看到插件已经可以正常运行,而不会弹出任何阻止宏执行的对话框。注意:将插件直接放到自启动路径中也可以实现不弹出阻止宏运行的对话框,且默认允许所有代码运行。但是它的缺点是不在“加载宏”列表中出现,不便于随时卸载与安装。而利用“加载宏”对话框安装的工具可以在方框中打勾或者不打勾来表示安装与卸载。2.4 使用 VBA 帮助Excel 的 VBA 具有详尽的帮助供用户学习、参考。通过帮助可以使用用户学得更快,对对象、属性、方法等也掌握更准。2.4.1利用帮助学习 VBA 语法VBA 有三种帮助:即时提示、本地帮助和在线帮助。1.即时提示即时提示是指用户录入代码时,VBA 弹出的选项,也称快速信息。它可以对属性、对象、方法和参数等等进行提示,使用户可以快速而准确地录入代码。例如需要输入获取单元格地址的代码,但又无法确定记忆中的单词是否正确,就可以借用提示来完成。从快速信息录入代码不仅快速,而且绝对正确。具体步骤如下:(1)录入代码“Range ("A1")”;(2)继续录入一个半角状态下的小数点,程序弹出即时信息——下拉列表,其中“Address”即表示区域的地址,见图 2.7 所示;(3)按下键盘上的下箭头,移动到“Address”处再按下 Tab 键,完整的单词即自动追加到代码中。如果手工录入,可能会录入为诸如“Adress”或者“Addrees”等等错误代码。如果在代码中录入函数,以及左括号时,仍然会产生即时帮助信息提示。不过不是下拉列表,而是参数所有参数的提示,及每个参数的类型,见图 2.8 所示。Excel VBA 程序开发自学通2020-2-26图 2.7 即时帮助之属性示第 19页 /共 508页图 2.8即时帮助之参数提示2.本地帮助本地帮助是指安装 Office 时安装的帮助文件,存放在本地磁盘中。调用本地帮助的步骤如下:(1)打开 Excel 2007,利用快捷键【Alt+F11】打开 VBA 编辑器;(2)按下快捷键【F1】打开 VBA 帮助窗口。窗口的标题是 Excel 帮助,和在工作表中按下【F1】调出的帮助窗口标题一致,然而其功能却大大不同。(3)在帮助窗口右下角可以看到“脱机”二字,表示当前调用的帮助信息是本地帮助。在左上角的的文字框中输入待了解的信息“names”,并单击回车或者左键单击“搜索”按钮,立即出现关于“names”的所有帮助信息,包括“names”的对象、方法、属性和实例。从窗口中可以看出关于“names”的帮助有 30 条,本页仅显示 1到 25 条;图 2.9调用 names 的本地帮助(4)单击其中第三项“Workbook.Names”,窗口中立即出现工作簿级名称集合的相关帮助信息,包括语法、说明和示例,见图 2.10 所示;(5)如果需查看关于“names”的其它帮助,则单击工栏具的图标返回,再从Excel VBA 程序开发自学通2020-2-26第 20页 /共 508页列表中选择目标帮助信息。也可单击“下一页”按钮查看其它帮助。图 2.10Workbook.Names 的帮助3.在线帮助在线帮助是指来自网络的在线信息。单击帮助窗口右下角的“脱机”,将弹出“连接状态”提示框,见图 2.11 所示。从中选择“显示来自 office online 的内容”即可调用在线帮助。在图 2.12 中显示了关于“names”的在线帮助。Excel VBA 程序开发自学通图 2.11 调整为显示在线帮助2020-2-26第 21页 /共 508页图 2.12显示关于 names 的在线帮助当然,除了上述三种帮助,可以有到国内外的 Office 论坛获取专家们的在线指导。例 如 Excelhome 论 坛 ( http://club.excelhome.net ) 、 Officefans 论 坛( http://www.officefans.net/cdb/index.php ) 或 者 太 平 洋 软 件 论 坛 Office 专 区(http://softbbs.pconline.com.cn/f251.html)等等。2.4.2捕捉错误VBA 对于代码出错时具有良好的捕捉功能,可以提示用户代码有误,甚至给出错误类型、突出标示错误语句,对于部分错误还会提示或者定位于出错的代码上,使用户可以及时更正。错误捕捉体现在编译错误和运行时错误两方面:1.编译错误编译错误指在 VBA 内部无法编译代码时产生的错误,大部分时候在编写代码时就能出现编译错误的提示,而不需要等到执行代码。产生编译错误时,VBA 会进行提示,让用户对有误的代码做修正。现例举四个编译错误,其中红色代码表示出错的语句。(1)按下快捷键【Alt+F11】进入 VBE 窗口,单击菜单【插入】\【模块】,然后在模块代码窗口中输入以下代码“Sub 11()”,当敲下回车键后,VBA 会弹出一个错误提示,见图 2.13 所示。该错误是使用了不正确的标示符做为宏名称引起的。宏名称不以数字开头。(2)在模块代码窗口中输入代码“Sub 我的宏()”,然后回车,程序会自动将 Sub程序之外壳补充完整,成为:Sub 我的宏()End Sub然后在中间录入以下语句:Excel VBA 程序开发自学通2020-2-26第 22页 /共 508页IF [a1]=10当敲下回车键后,立即弹出图 2.14 所示错误提示,它表示 IF 语句未录入完整。完整的代码应是 IF…Then…不是仅仅有 IF 忽略 Then。图 2.13 错误提示一图 2.14 错误提示二图 2.15 错误提示四图 2.15 错误提示四(3)将前面代码中的修改“if [a1]=10”修改为“Range(“a1”)=10”,当敲下回车键后立即弹出图 2.15 所示的错误提示。该提示表示代码中使用了无效字符,其中无效字符指的全角双引号,Range 的参数只能使用半角双引号。(4)将前面代码中的修改“Range(“a1”)=10”修改为“Mid([a1])=1”,当敲下回车键后立即弹出图 2.16 所示的错误提示。该提示表示 Mid 函数的参数不全,也就是缺少逗号分隔符“,”。在[a1]后面加两个逗号与参数就可以消除错误。2.运行时错误运行时错误是指代码编写时无法判断,在执行程序时才出现的错误。下面也举四例运行时错误。(1)在模块代码窗口录入以下代码,表示获取第 11 个工作表的表名:Sub 取表名()MsgBox Sheets(11).NameEnd Sub如果当前工作簿中工作表数量不足 11 个,那么按下【F5】键执行程序时,将产生图 2.16 所示的错误提示。其中下标越界表示引用了不存在的对象,工作表仅三个时引用第 11 个就会越界。Excel VBA 程序开发自学通2020-2-26第 23页 /共 508页图 2.16 运行时错误 1(2)在模块代码窗口录入以下代码,表示对 Z2048576 单元格填充红色背景。Sub 填充红色()Range("Z2048576").Interior.ColorIndex = 3End Sub录完代码后,按下【F5】键执行程序,立即弹出图 2.17 所示错误提示。因为 Excel2007 最多 1048576 行,不存在 Z2048576 这个单元格,所以对该单元格进行任何操作都会失败。图 2.17 运行时错误 2(3)在模块代码窗口录入以下代码,用于将当前表数据区域的字体大小修改 420磅。Sub 设置字体大小()ActiveSheet.UsedRange.Font.Size = 420End Sub录完代码后,按下【F5】键执行程序,立即弹出图 2.18 所示错误提示。因为字体只支持 1 到 409 之间,而代码中 420 已经超过 Excel 的允许范围,所以报错。Excel VBA 程序开发自学通2020-2-26第 24页 /共 508页图 2.18 运行时错误 3(4)在模块代码窗口录入以下代码,用于统计 A 列数字个数。Sub 计算 A 列数字个数()Dim i As Bytei = WorksheetFunction.Count(Range("A:A"))MsgBox "A 列数字个数为:" & iEnd Sub录完代码后,按下【F5】键执行程序,如果 A 列有 1000 个数字,那么会立即弹出图 2.19 所示错误提示。代码为变量 i 为 byte 类型,那么它只能表示 0 到 255 之间的数值,当 A 列数字个数超过 255 个时就会产生错误提示。图 2.19 运行时错误 4根据以上分析,Excel VBA 有着完善的错误捕捉功能,用户的代码不管在运行前还是运行后产生错误,都可能通过错误提示帮助用户获取错误信息。不仅知道代码中存在错误,而且可以明确地看到错在哪一句代码。例如“计算 A 列数字个数”这段代码中,当产生错误提示框时,单击“调试”按钮,程序会将出错的代码行黄色显示,且在其左方用箭头标示,见图 2.20 所示。图 2.20 标示错误语句以上错误捕捉功能需要设置好的前提下才可能发挥作用。在下一章中,将重点介绍 VBE 环境及其选项设置。Excel VBA 程序开发自学通第三章2020-2-26第 25页 /共 508页巧设 VBA 编辑器提升编程效率VBA 是 Visual Basic for Application 的简称,表示一种程序语言。VBE 是 VisualBasic Edirtor 的简称,它是 VBA 的容器,用于存放 VBA 代码。设置好 VBE 对 VBA代码的编写、使用有交大的帮助。本章要点: 认识 VBE 组件 VBE 中选项设置3.1 认识 VBE 组件VBE 窗口中包括很多组件,包括菜单栏、工具栏、代码窗口、立即窗口等等,有效地利用这些组件可以对开发插件、录入代码、检测错误有着举足轻重的作用。3.1.1访问 VBA 开发环境从本节开始,正式进入代码编写环节。在编写代码前,有必要认识一下代码的容器:VBE。进入 VBE 的方法有三种:功能区按钮法、快捷键法和右键菜单法1.功能区按钮法Excel 2003 中可以通过菜单【工具】\【宏】\【Visual Basic 编辑器】来进入 VBE界面,而 Excel 2007 在默认状态下隐藏了该菜单。在 2007 中需要设置 Excel 选项、调出【开发工具】功能区。具体步骤如下:(1)单击菜单【Office 按钮】\【Excel 选项】打开选项对话框;(2)在对话框中将“在功能区显示“开发工具”选项卡”打勾,单击“确定”按钮后,在功能区即可出现【开发工具】功能区。见图 3.1 所示:Excel VBA 程序开发自学通2020-2-26第 26页 /共 508页图 3.1 调出开发工具功能区(3)单击功能区中的“Visual Basic”按钮,VBA 编辑器立即呈现出来。2.快捷键法调出 VBA 编辑器的快捷键是【Alt+F11】。而在 VBA 编辑器中再次使用快捷键【Alt+F11】则可以缩小 VBA 编辑器的窗口并返回到 Excel 工作簿窗口。如果需要关掉 VBA 编辑器再返回 Excel 工作簿窗口,则可以使用快捷键【Atl+Q】。3.右键菜单法在工作表标签中单击右键,弹出的菜单中将出现【查看代码】菜单,见图 3.2 所示。单击该子菜单即可以入 VBA 编辑器。右键菜单法进入 VBA 编辑器与前面两法稍有分别,它进入 VBA 编辑器后会定位于当前工作表的窗口。例如选择“月报表”工作表后再单击右键,从【查看代码】菜单进入 VBA 编辑器界面后,会自动选定“月报表”工作表,右边也相应显示“月报表”的代码窗口,见图 3.3 所示。图 3.2 从右键菜单进入 VBE3.1.2图 3.3 定位于“月报表”认识 VBE 的组件VBA 有很多组件:菜单栏、工具栏、工程资源管理器、属性窗口、代码窗口、对象与过程窗口、立即窗口、本地窗口、监视窗口、对象窗口以及工具箱。不同组件Excel VBA 程序开发自学通2020-2-26第 27页 /共 508页有不同作用,不过菜单与工具栏会出现重复功能。VBE 中的所有组件无法同时出现,某些组件与组件之间是排斥关系。如组件 A出现,那么组件 B 就会关闭或者隐藏,反之亦然。在图 3.4 中罗列了大部分组件。1.菜单栏VBE 中的菜单栏包含了 VBE 中大部分功能。单击二级、三级单菜单可以执行文件导出、导入、查找、删除、新建组件、设置格式、定义选项、加密码、代码调试、窗口切换、调用帮助等等功能。另外还有一种快捷菜单,即右键菜单。右键在不同地方将产生不同菜单,例如代码窗口和在窗体中、立即窗口所产生的右键菜单就大大不同。即使同一代码窗口,在不同的状态下,仍然也会有不同的菜单,例如正常状态和中断状态下,代码窗口的右键菜单也不一样的。菜单栏和所有右键菜单可以利用 VBA 来定制,包括创建新菜单,删除、隐藏原有菜单等等。在本书第 29 章将讲述 VBE 中定制菜单和工具栏。图 3.4VBE 部分组件2.工具栏工具栏包含功能在菜单中都有,不过工具栏的按钮在操作上比菜单栏方便、直观,所以工具栏在工作中使用率会高于菜单栏。工具栏同样可以定制,创建或者删除工具栏都可以,在本书第 28 章将会讲述。工具栏中的按钮可以通过功能提示来查看名称及了解功能。只要将鼠标指针移向任何一个按钮,屏幕上将出现它的名称和快捷键(如果有的话)。图 3.5 即为“复制”按钮的功能提示和快捷键提示。工具栏中包括四条组成,默状态下可能仅显示“标准”工具栏。如果需要调出其它工具栏,可以在工具栏或者菜单栏中单击右键,在需要显示的工具栏名称上单击使其打勾即可,见图 3.6 所示。Excel VBA 程序开发自学通2020-2-26图 3.5 工具栏的屏幕提示第 28页 /共 508页图 3.6调出“编辑”工具栏3.工程资源管理器工程资源管理器用于管理 VBA 工程及其对象。一个工作簿有一个工程,默认名称为“VBAProject”;每个工程有多个对象,包括工作表、窗体、模块等等。在工程资源管理器中可以管理无数个工程,如图 3.7 所示:如果进入 VBE 界面后未显示工程资源管理器,可以使用快捷键【Ctrl+R】调出。工程资源管理器是以目录树形显示当前的所有工程,以及每个工程中的子对象。如果需要查看或者编辑“Sheet1”中的代码,进入工程资源管理器中双击“Sheet1”,右边马上会显示“Sheet1”中所有代码;如果需在查看或者修改用户窗体“UserForm1”中的代码,则需要在“UserForm1”上单击右键,从弹出的快捷键单中选择“查看代码”,若双击窗则只能查看窗体本身。图 3.7 工程资源管理器图 3.8 查看窗体中的代码工程资源管理器中默状态下有一个工程,代表当前工作簿,以及与工作表数量对应的 Sheet1、Sheet2、Sheet3 和 Thisworkbook 对象。模块、用户窗体(UserForm)和类模块等等需要手工添加才会出现。4.属性窗口属性窗口用于设置、修改各种对象的属性。对象包括工程对象、窗体对象、模块对象及窗体中的图形、组合框、标签等等对象。而属性则包含很多很多项目,例如字Excel VBA 程序开发自学通2020-2-26第 29页 /共 508页体大小、颜色、边距、高度、标题、显示方式等等。而且不同的对象有各自己独特的属性。现以设置标签“Lable1”的名称、字体与对齐方式为例,演示属性窗口的具体用法。(1)在 VBE 界面中,单击菜单【插入】\【用户窗体】。此时在右边产出现一个新建窗体,同时出现“工具箱”,见图 3.9 所示。如果未出现“工具箱”,可以单击菜单【视图】\【工具箱】。(2)左键按下工具箱中的图标“A”即标签工具,然后在窗体中按下向右下方拖动,从而产生一个新标签,其默认名称为“Label1”;(3)在“Label1”呈选择状态下按下快捷键【F4】调出属性窗口,见图 3.10 所示;图 3.9 新建窗体图 3.10 显示标签的属性(4)进入属性窗口,找到“Caption”属性,在右边录入“请输入用户名:”(5)找到“字体”属性,单击右边的图标,将弹出一个字体对话框。从对话框中分别选择“黑体”、“粗体”、“三号”,然后单击“确定”返回;(6)找到“TextAlign”属性,单击其右边的列表框,将弹出三个选项,表示三种对齐方式。见图 3.11 所示。其中第二项表示居然对齐,选择第二项;(7)因字体加大,标签显示不完整,手工调整到适合大小后,效果见图 3.12 所示:图 3.11 设置标签文字的对齐方式图 3.12 设置属性的标签效果其它属性也可以用类似方法设置。即在对应的属性右边的文字框中录入字符,或者在弹出的对话框中设置选项,以及从下拉列表中选择需要的选项。用户举一反三可以学会所有属性的设置。Excel VBA 程序开发自学通2020-2-26第 30页 /共 508页5.代码窗口代码窗口是用于存放 VBA 代码的处所,它是 VBE 中最核心的组件。代码窗口包括工作表代码窗口、工作簿代码窗口、窗体代码窗口、模块代码窗口和类模块代码窗口。6.对象与过程窗口对象与过程窗口是指位于代码窗口上方的对象列表和窗过程列。见图 3.13 所示。图 3.13 对象与过程列表图 3.13 中左上角的下拉列表是对象列表,单击下拉箭头可以罗列出所有可用的对象名称;右边的下拉列表是可用的过程列表,单击下拉箭头可以罗列出所有可用的过程名称。这两个列表对用于辅助代码录入,以及提示当前对象所支持的对事件。用户也可以永远不用它,采用手输入代码。但是在输入工作表事件或者工作簿事件时,通过对象与过程下拉列表自动产生代码比手工输入的效率更高,且更准确。关于它们的用法在后面关于事件的章节会详细的说明。7.立即窗口立即窗口有两个功能:显示调试代码时产生的结果(信息),以及执行单句的代码。立即窗口默认是隐藏状态,可以使用快捷键【Ctrl+G】将其调出。现分别演示立即窗口的两种功能与操作步骤:(1)在开启任意工作簿后按下快捷键【Alt+F11】进入 VBE 界面;(2)单击菜单【插入】\【模块】;(3)按下快捷键【Ctrl+G】显示立即窗口;(4)在模块中输入以下代码:Sub 显示当前工作簿全名()IF Len(ThisWorkbook.Path) = 0 ThenDebug.Print "当前工作簿未保存"ElseDebug.Print ThisWorkbook.FullNameEnd IFEnd Sub(5)将光标定位于代码中任意位置,单击快捷键【F5】执行代码,在立即窗口将会显示代码执行结果。如果当前工作簿未保存,则立即窗口显示“当前工作簿未保存”,则否显示工作簿全名,包括其路径。见图 3.14 所示;Excel VBA 程序开发自学通2020-2-26第 31页 /共 508页(6)清除立即窗口中的字符,然后录入以下代码:Workbooks.Add (xlWBATChart)然后单击回车键,注意必须是光标位于当前代码行最右边时单回车,此时可以发现 Excel 新建了包含一个工图表的工作簿。(7)在第二行输入以下代码然后回车,则当前工作簿中立即新建 10 个工作表。Sheets.Add Count:=10执行代码后,工作簿中将有 13 个工作表,见图 3.15 所示。图 3.14 在立即窗口显示信息图 3.15 在立即窗口执行单行程序8.工具箱工具箱对于 VBA 程序开发是非常重要的工具。默认状态工具箱包括了 15 种工具,用户可以利用这些工具设置出和其它任何软件程序类似的界面。如果默认工具不够用,还可以右键定义新的工具。调用工具箱的方式和前面的任何组件的方式都不同,它是建立在 UserForm 的基础上的。当用户选择 UserForm 对象时才出现,其它状态下一律隐藏。显示工具箱的方法是单击菜单【插入】\【用户窗体】,此时工具箱将自动显示出来。工具箱的外观见图 3.16 所示。如果已经有窗体,不想再建立窗体,则可以双击窗体名(默认为 UserForm1,根据实际情况,用户可能修改为其它名称),然后单击菜单【视图】\【工具箱】即可。工具箱是可以定制的,包括新建页、附件控件等等。步骤如下:(1)在窗体的右上角空白区单击右键,从菜单中选择【新建页】即可建立一个名为“新页”的页面;(2)在“新页”二字上面单击右键,从菜单中选择【重命名】,并录入名称“我的新工具”;(3)新建的页是完全空白的,可以对其任意添加新的组件。在当前页中间空白区单击右键,从菜单中选择【附件控件】,弹出“附件控件”对话框;(4)在对话框中将需要的组件打勾,然后返回工具箱。工具箱的定制效果见 3.17所示:Excel VBA 程序开发自学通2020-2-26图 3.16 工具箱外观3.1.3第 32页 /共 508页图 3.17 定制工具箱VBE 中不同代码窗口的作用前一小节中谈过 VBE 界面中的代码窗口用于存放 VBA 代码,但是了解这一点还远远不够。在 VBE 中有五类代码窗口,代码在不同窗口中将产生不同作用,哪怕代码完全一致。1.工作表代码窗口工作表代码窗口用于存放工作表事件代码,该代码仅仅在当前表中调用。普通Sub 过程保存在工作表事件代码中虽然也可以执行,而且其它模块或者工作表也可以调用,但却有诸多不便。所以正常情况下,大家都达成一个共识:工作表事件代码存放工作表代码窗口,Function 过程和 Sub 过程保存在模块中。工作表代码窗口的开启方式为:使用快捷键【Ctrl+R】调出工程资源管理器,然后双击工作表名称,右边立即出现工作表事件代码窗口。每个工作表都有自己的代码窗口,在窗口中储存自己的事件相关的代码,该代码只有在当前表才可以调用。例如在“生产表”的代码窗口录入以下报告选区地址的代码,见图 3.18 所示:Private Sub Worksheet_SelectionChange(ByVal Target As Range)MsgBox Target.AddressEnd Sub图 3.18在“生产表”代码窗口录入 SelectionChange 事件代码使用快捷键【Alt+Q】返回 Excel 工作表,在“生产表”工作表中选择任意区域,Excel VBA 程序开发自学通2020-2-26第 33页 /共 508页立即弹出当前选区地址的信息,见图 3.19 所示。如果选择多个区域,同样提示多区域的地址,中间用逗号分隔,见图 3.20 所示。而在其它任何工作表选择区域则没有任何反应。如果一定要在其它工作表调用当前工作表事件的代码,也可以采用以下步骤完成:(1)将“生产表”中代码前的 Private 删除;(2)在“Sheet2”的代码窗口录入以下代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)Call Sheets("生产表").Worksheet_SelectionChange(Target)End Sub其中 call 表示调用其它过程。按下【Alt+F11】返回工作表,进入 Sheet2 工作表中,选择任意区域也会同样弹出选区地址信息。图 3.19提示选区地址图 3.20 提示多区域地址注意:工作表事件的代码中 Private 表示将当前 Sub 程序声明为私有,即只有当前工作表模块或者工作表才可以调用。本例中为了让 Sheet2 中可以调用,必须去除 Private。对于 Private 的更多知识,参阅本书第五章。2.工作簿代码窗口工作簿代码窗口的名字为 ThisWorkbook,该窗口用于存放工作簿级别的事件代码。虽然它也可以存放 Function 过程和普通的 Sub 过程,但根据习惯,以及使用上的方便性,该窗口仅仅存放工作簿事件相关代码。例如图 3.21 是工作簿级别的事件代码,表示不管任何时候关闭工作簿都保存一次。该代码仅仅在关闭工作簿时执行,其它任何窗口无法调用该事件代码。图 3.21 工作簿及关闭事件的代码Excel VBA 程序开发自学通2020-2-26第 34页 /共 508页3.窗体代码窗口窗体代码窗口用于存放窗体、控件相关的代码。它的代码只能在窗体中使用,其它任何窗口无法执行。查看窗体中代码的方法是在工程资源管理器中的窗体上单击右键,从菜单中选择【查看代码】。例如图 3.22 中的代码位于 UserForm1 的代码窗口,表示启动窗体时设置它的左边距为 100。在 UserForm1 以外的任何窗口以任何方式都无法调用此代码。图 3.22窗体代码窗口4.模块代码工作中使用最多的就是模块代码窗口。在模块代码窗口中存放 Sub 过程和Function 过程。这些过程可以在当前模块执行,也可以供其它任何窗口调用。工作表事件、工作簿事件、窗体事件和类模块都可以使用模块中的程序,而模块与模块之间也可以相互调用。5.类模块类模块是用户自定义类的属性和方法的模块。单击菜单【插入】\【类模块】即可创建一个类模块,其图标为。类模块的代码通常用于应用程序级别的事件,在应用程序对应的事件中调用该代码。在本书第十五章将详述类模块的用法。3.2VBE 中选项设置VBE 中的选项设置对于 VBA 爱好者来说至关重要,如果该选项设置不当,会对编程带来无限烦恼。例如无法捕捉错误、没有函数提示、控件无法对齐等等。打开 VBA 编辑器选项的方法是单击菜单【工具】\【选项】。选项对话框外观见图 3.23 所示。本节对选项对话框中所有组件做详细讲解,并建议如何优化设置。Excel VBA 程序开发自学通2020-2-26图 3.233.2.1第 35页 /共 508页VBE 选项编辑器选项选项对话框中第一个选项卡即可“编辑器”。该选项卡中各项目功能介绍如下:1.自动语法检测自动语速法检测是指编写代码时自动对每句代码检查是否有错误,如果有错误则弹出警告框,同时将错误的语句红色显示,提示用户代码有误。例如下图中,在输入 For 语句时忘记了 In,使代码产生错误。当录入该句代码并回车时,程序会立即弹出一个编译错误的提示,告知错误类型,同时红色标示错误语句。图 3.24 自动语法检测建议勾选此选项,以协助自己了解当前代码中的错误类型,从而快速修正。2.要求声明变量该选项表示强制用户在编写代码时声明所有变量,否则程序无法执行。其具体表现为在任何新建模块、工作表代码窗口和工作簿代码窗口都产生“Option Explicit”语句。强制声明变量的优点有三个: 提升代码运行效率 防止因变量类型错误带的错误 在输入对象变量时可以自动列出快速信息Excel VBA 程序开发自学通2020-2-26第 36页 /共 508页从图 3.25 中可以看到,变量 rng 未声明,所以运行代码时会产生一个编译错误,提示变量未定义。图 3.25 提示变量未定义错误建议勾选该选项。3.自动列出成员该选项可以在录入代码时自动产生语法提示,包括类型与对象的属性、方法等等。例如在输入“dim rng as ”语句时,VBA 会列出所有可用的变量类型供用户选择,而不需要手工录入,从而防止输入错误的变量类型,见图 3.26 所示和 3.27 所示:图 3.26 自动列变量名称图 3.27 自动列表对象的属性与方式如果不勾选该选项,则无法弹出提示,只能手工录入。建议勾选。4.自动显示快速信息该选项表示在录入代码时对参数进行提示,方便用户核查录入的参数是否正确。该提示主要体现在三方面: 参数个数 参数类型 当前参数从图 3.28 的快速信息中可以看出,MID 函数有三个参数,第一参数是 String 型,第二参数是 Long 型,第三参数是可选参数,它代表长度……这些信息都为编程提供了便利。而图 3.29 的快速信息可以看出,Range 有两个参数,第二个参数是可选参数。当前正在录入第二个参数,因为第二参数已加粗显示。Excel VBA 程序开发自学通2020-2-26图 3.28 提示 MID 的参数信息第 37页 /共 508页图 3.29 提示当前参数建议勾选该选项。5.自动显示数据提示该选项是指中断模式下,设定一个断点,则当执行代码并且鼠标指针指到变量上面时,数据提示窗口就会显示出变量的值。具体操作步骤如下:(1)在模块中输入以下代码:Sub Test()Dim temp As Bytetemp = 100MsgBox tempEnd Sub(2)在第四句代码在前面单击一次,表示将该句设置为断点。也可以光标定位于该句代码,然后按下 F9 来设置断点;(3)按下 F8 进入调试语句状态,它后逐句执行代码。在执行代码时将鼠标指针指向 Msgbox 后面的变量 temp 上,查看提示信息;(4)在多次按下 F8 后,该提示会产生变化。因为变量 temp 在初期值为 0,而在“temp=100”语句执行后就变成了 100,所以提示信息也会相应的变化。图 3.30 数据提示 1图 3.31 数据提示 2本选项用途不是很广,用户可以勾选也可以不勾选。6.编辑时可拖动文本该选择表示用鼠标可以拖动代码,等同于剪切、粘贴之功能。图 3.32 中演示了从 test1 程序中将其代码拖到 test2 程序中的方法。具体步骤为:(1)选择需要拖动的代码;(2)按下左键不放,鼠标下会呈现一个虚框,表示当前可以拖动代码;(3)拖到目标位置后松开鼠标。如图 3.32,当前的插入点是过程 test2 的第一行,当松开鼠标后,代码就会插到入该位置。类似于剪切、粘贴。最后效果见图 3.33 所示。图 3.32 拖动代码建议勾选该选项,以方面代码移动。图 3.33 拖动后的效果展示Excel VBA 程序开发自学通2020-2-26第 38页 /共 508页7.缺省查看所有模块该功能是在模块代码中显示所有过程的代码。当模块中有多个过程时,该功能极其有用,减少切换时间。如果不勾选该选项,窗口中仅仅显示当前过程或者声明部分的代码。如果需在查看另一个过程代码需要从过程下拉列中切换。见图 3.34 所示。建议勾选该选项,使代码阅读更方便。8.过程分隔符该选项表示利用一条横线将多个过程的代码或者变量声部分开。图 3.34 是不分隔代码时的外观,可以发现它的缺点是不利于代码查看。与图 3.33相比不够分明。图 3.34切换显示不同过程图 3.35 未分隔所有过程及声明建议勾选该选项,使代码阅读更方便。9.自动缩进该选项表示定位代码的第一行后,所有接下来的代码会在该定位点开始。更通俗地说法即为统一设定左边距。如果勾选该选项,那么第一行左边距为 4 时,第二行默认状态也是 4,否则默认为 0.建议勾选该选项,使代码更美观,也方面阅读。3.2.2编辑器格式选项编辑器格式选项卡中包括了所有与代码显示方式相关的选项。它可以决定不同代码如标准文本、断点、标签等等以什么字体、颜色、大小显示来出,使用户方便区分。在默认状态下,VBA 的设置已经非常合理,虽然用户可以随意定制,但建议不要做任何修改。图 3.36 即编辑格式选项卡的界面,而图 3.37 是默认状态下的执行点、标签和断点样式。Excel VBA 程序开发自学通图 3.36 编辑器选项卡3.2.32020-2-26第 39页 /共 508页图 3.37 默认状态下的代码格式通用选项通用选项的项目较多,其中最重的几个项目包显示网格、错误捕捉和显示工具提示。通用选项的外观1.显示网格该选项是针对用户窗体的,即窗体在设计状态下,显示网格线,而窗体中的控件也以网格为基准对齐。它的优点是窗体内有多个控件时可以轻松地对齐。图 3.38 是通用选项卡的外观,图 3.39 是勾选“显示窗线”和“对齐控件到网格”状态下的窗体。图 3.38 通过选项卡图 3.39 让设计状态的窗体显示网格对于“对齐控件到网格”可根据需要来设置。当需要多控件对齐时可勾选该选项;在对控件微移时则不能勾选。比对某控件需要向右微调 4 个单位,另一个控件需要向左微调 3 个单位,那么对齐控件到网格后就无法完成。Excel VBA 程序开发自学通2020-2-26第 40页 /共 508页2.显示工具提示该选项表示工具栏的所有按钮。如果勾选该选项,鼠标指向按钮时可以显示它的功能与快捷键提示,这对于学习 VBA 有较大的帮助。3.错误捕捉错误捕捉包括“发生错误时则中段”、“在类模块中中断”和“遇到未处理的错误时中断”三个选项。各选项含义见表 3-1 所示。表 3-1 错误捕捉含义类型发生错误则中断在类模块中中断在失去句柄错误时中断含义任何错误都会使工程切换到中断模式,不管错误处理程序是否为活动的,也不管代码是否在类模块中。任何在对象类模块中失去句柄的错误会让工程进入中断模式。任何其他失去句柄的错误会让工程进入中断模式。其中第一项和第三项可以演示一下差异。(1)清空当前表 A1:B1,在模块中录入以下代码:Sub 错误捕捉()On Error Resume NextDim i As Bytei = [a1] / [b1]IF err.Number 0 Then GoTo err:MsgBox iExit Suberr:MsgBox "出错了"End Sub(2)将错误捕捉设为第一项;(3)光标定位于代码中任意位置,按下快捷键【F5】执行代码,程序立即弹出运行时错误对话框;(4)从对话框中单击“调试”按钮,“i = [a1] / [b1]”语句呈黄色显示,表示该语句在错误;(5)将错误捕捉设置为第三项后再执行代码,程序不再弹出错误提示。调试代码或者初学时,应该将选项设置为第一项;代码编写完成正式执行时则需要设置为第三项。3.2.4VBA 代码保护VBA 代码是需要保护的,基于两种目的:保护成果、防止无意中破坏。保护 VBA 代码有两种方式:共享工作簿、代码加密。1.共享法共享法是指对工作簿共享,从而实现不可查看代码的目的。听起来像悖论,按下Excel VBA 程序开发自学通2020-2-26第 41页 /共 508页面的步骤操作却一定可以达成需求。(1)在工 VBA 模块中录入代码后按下快捷键【Ctrl+S】保存工程,必须是 xls格式或者 xlsm、xlam、xla 格式;(2)返回工作表界面,进入【审阅】功能区,单击【共享工作簿】按钮,将“允许多用户同时编辑”打勾,然后保存;(3)重新打开该工作簿,进入 VBE 界面后双击当前工作簿工程,将弹出 3.41所示的提示:图 3.40 共享工作簿图 3.41保护后状态2.加密法VBA 的选项对话框提供了代码加密的功能,步骤如下:(1)在 VBE 界面下单击菜单【工具】\【VBAProject 属性】,每个工程的默认名称是“VBAProject”,但该名称可以手工修改。如果曾经修改过,以实际名称为准;(2)在“工程属性”对话框中,切换到“保护”选项卡,选择“查看时锁定工程”,并在下面的两个文字框录入两次相同的密码;(3)保护工作簿后重启该文件,使用【Alt+F11】进入 VBE 界面,双击工程名称时将弹出图 3.43 所示对话框,如果未录入正确密码将禁止查看。图 3.42 设置工程密码图 3.43 开启已保护的工程时确认权Excel VBA 程序开发自学通2020-2-26第 42页 /共 508页限上面的两种方法也可以同时使用。注意:不管如何设置,VBA 的保护措施是很脆弱的,不需要很专业的程序员就可以攻破它。所以如果对自己的代码安全性要求较高,可以使用 COM 加载宏,而不是 Excel VBA 编写。在本书第 32 章将讲述用 VB 开发 COM 加载项的具体方法。进阶篇:VBA 语法、过程与事件第四章VBA 基本概念VBA 语言是面向对象的一种程序语言。在 VBA 中有很多很多对象,每个对象涉及自己范畴的很多属性和方法,还拥有各自专属的事件。在本章中将详细介绍以上VBA 的基础知识。本章要点: 理解 VBA 的对象、属性与方法Excel VBA 程序开发自学通2020-2-26第 43页 /共 508页认识 VBA 的事件VBA 的运算符简单的字符处理函数4.1理解 VBA 的对象、属性与方法几乎 90%以上的 VBA 程序都是在操作对象,利用对象的方法来读取或者写入对象属性……在编写代码前必须对 Excel 的对象有全面的认识。4.1.1什么是对象一个最简单的故事都一定有人物、事件、或者时间、地点。人物是故事的核心,那么 VBA 也相应的有对象、属性、方法和事件,其中对象是 VBA 的核心。很多很多软件都带有 VBA 环境,那么 VBA 在不同软件中的操作对象也是不同的。如 WORD 中的 Application 对象是 Word,Excel 中的 Application 对象则是 Excel。VBA 对对象的操作语句格式总是遵循这样的格式:“对象.属性”、“对象.方法”或者“父对象.子对象.属性”……例如在下面的实例:Sheets("工作表").Name——Sheets("工作表")是对象,Name 是对象的属性Workbooks(2).Close——Workbooks(2)是对象,Close 是对象的方法Range("a1:a100").Comment.Delete——Range("a1:a100")是父对象,Comment 是子对象,Delete 是方法Excel 有数百个对象,表 4-1 是常见对象名称其及含义。表 4-1 常见对象及其含义对象名ApplicationWindowWorksheetSheetsShapeRangePivotTableWorkbookShapeRangeNameChartFileDialogCommandBarPopupCommandBar含义代表整个 Excel 应用程序。代表窗口代表一个工作表指定的或活动工作簿中所有工作表的集合代表形状区域,它是文档中的一组形状代表工作表上的数据透视表代表一个 Excel 工作簿代表绘图层中的对象,例如自选图形、任意多边形、OLE 对象或图片代表某一单元格、某一行、某一列、某一选定区域,或者某一三维区域代表单元格区域的定义名。名称可以是内置名称(如Print_Area)或自定义名称代表工作簿中的图表提供文件对话框,其功能与 Office 应用程序中标准的“打开”和“保存”对话框类似代表命令栏上的一个弹出式控件代表容器应用程序中的一个命令栏Excel VBA 程序开发自学通4.1.22020-2-26第 44页 /共 508页如何理解属性属性是一个对象的外部和内部特征,包括大小、颜色或边距、数量,或者某一方面的行为,例如对象是否可以激活、是否可见的、是否可以刷新等等。可以通过修改对象的属性值来改变对象的特性。可以打一个比方,桌子是一个对象,桌面是方形的、有四只脚、由木头组成、可以拆解等等就是属性。而拆解这个动作则属于方法。一个工作表具有哪些属性?可以用下列两种方法获取。1.自动成员列表任何对象都有属性,而且在录入代码时可以从自动成员列表中看到其属性。例如图 4.1 中,输入“worksheets.”后将会看到一个下拉列表,在该列表中包含了工作表的属性及方法,其中带有手形图标的是属性,另一种是方法。然而很多很多对象还有一些隐藏属性,在该列表中没有罗列出来。可以按下列步骤调用其隐藏属性。(1)按下快捷键【F2】打开对象浏览器;(2)在空折区单击右键,从菜单中选择【显示隐含成员】,见图 4.2.(3)再返回模块代码窗口,输入“worksheets.”后将会看到一个新的下拉列表,里面有灰色的隐藏属性。图 4.1 工作表属性列表图 4.2 从对象浏览器调整显示隐藏属性然而,这种方式仍然有局限制,即下拉列表即有属性又有方法,不利于查看。所以方法 2 可以有针对性的查看属性。2.查看帮助在 VBA 的帮助中有着完善的对象、属性、方法查询系统。仍然 Worksheets 对象为例,需要查看其属性的步骤如下:(1)在 VBE 窗口中按下快捷键【F1】调出帮助窗口;(2)在搜索栏中输入“Worksheets 对象成员”并回车;(3)查找结果中包含了 100 项关于 Worksheets 对象成员的信息,见图 4.3 所示。单击第一项“Worksheets 对象成员”即可查看到 Worksheets 对象的成有属性和方法属性和方法是分开罗列出来的,见图 4.4 所示。Excel VBA 程序开发自学通2020-2-26图 4.3 查找 Worksheets 对象成员4.1.3第 45页 /共 508页图 4.4 帮助中的 Worksheets 属性列表如何理解方法方法指的是对象能执行的动作。它是一个动词,而对象是一个名词。但是初学者需要记住,它的语法与汉语语法是不同的。“创建工作表”:创建是动词,表示方法,“工作表”是名词,代表对象。在 VBA 中却需要对象在前,方法在后,示例如下:worksheets.add:worksheets 是工作表对象,add 是方法,表示新建。和 4.1.2 小节中的办法一致,在帮助中可以到关于 worksheets 的所有方法,见表4-2 所示:表 4-2 worksheets方法一览名称AddCopyDeleteFillAcrossSheetsMovePrintOutPrintPreviewSelect4.1.4说明新建工作表、图表或宏表。新建的工作表将成为活动工作表将工作表复制到工作簿的另一位置删除对象将单元格区域复制到集合中所有其他工作表的同一位置将工作表移到工作簿中的其他位置打印对象按对象打印后的外观效果显示对象的预览选择对象判断对象的属性与方法从图 4.1 中可以看出,每个对象的属性和方法都同时出现在成员列表中。那么如Excel VBA 程序开发自学通2020-2-26第 46页 /共 508页何区分哪个项目是属性,哪一个是方法呢?主要要三种办法:1.根据自动成员列表中的图标判断成员列表中带绿色图标是方法,另一种图标即为属性。2.看帮助在 VBA 的帮助中有完善的解说,从中可以看到其性质及功能详解。只是每一个列表中的成员都逐一查看效率不高。3.判断词性方法是动词,它有一个动作,每个动作会产生一个可见的结果。例如:Worksheets.Add:Add 动作的结果是产生一个新工作表Worksheets.Select:Select 动作的结果是选择当前工作簿所有工作表Worksheets.PrintPreview:PrintPreview 动作的结果是当前表进入预览状态而属性是名词,表示一种状态或者特征,可以将对象的这个状态显示在单元格中。例如:[a1] = Worksheets.Count:Count 表示工作表的数量,执行本语句可以在 A1 单元格返回一个值[a1] = Worksheets.Item(1).Name:Item 是工作表集合的属性,代表 Worksheets 的子集,但 Item 本身却是一个对象集合,在本例中,Item(1)代码第一个工作表[a1] = Worksheets(1).Visible:Visible 表示工作表的可见性属性,可以读取,也可以改变这一属性。如 Worksheets(1).Visible=False 即可将第一个工作表隐藏起来4.2认识 VBA 的事件所有对象都有属性与方法,同时也具有事件。充分地利用事件可以使程序实现自动化。4.2.1什么是事件事件是对象在某个状态下引发的动作。每个对象都会有很多的事件,不同的事件有不同的触发条件。如果更形象地阐述,路人甲不小心踩了路人乙,路人乙则骂他“没长眼?”。在这个过程中,被踩是一个事件。路人甲踩了路人乙则引发路人乙“被踩”的事件,而路人乙回骂则是事件所触发的动作。同一个事件可以触发多个动作,例如路人乙骂完之后再踢回一脚等等。而 Excel 的事件也是类似的情形。例如以下代码:Private Sub Workbook_Open()Sheets(1).Select[a1]=dateEnd Sub此代码是一个工作簿开启事件,由开启事件引发了两个动作:进入第一个工作表Excel VBA 程序开发自学通2020-2-26第 47页 /共 508页和在 A1 单元格显示当前日期。4.2.2事件的分类及其用途VBA 有很多类事件,分类的标准由对象来决定。表 4-3 是对象其及事件的对应关系表。其它分类方式在后面的章节将进行介绍。表 4-3对象ApplicationWorkbookWorksheetChartUserFormLabelImage事件分类事件应用程序事件工作簿事件工作表事件图表事件窗体事件标签事件(窗体中的控件)图像事件(ActiveX控件)表中并没有罗列完所有事件。因为每一个控件都有自己的事件,而 VBA 可以调用的对象有超过 100 种。虽然具有事件的对象很多,不过常用的是工作簿事件、工作表事件及窗体事件。事件在工作中非常有用,常用于实现过程的自动化。例如工作簿打开时自动执行某程序,工作表切换也自动执行一个程序,鼠标移动窗体时……通过这些事件的运用,可以在某个条件全自动执行数据计算或者环境设置、变量赋值等等,从而减少手工执行代码,提升工作效率。对于事件的具体用法和实例,在本书第 8 章中将再详述,本章仅仅了解基本概念。4.3 VBA 的运算符VBA 最主要的两项任务是计算和界面设计。在进入程序运算前需要了解 VBA 中有哪些运算符,以及其运算规则。4.3.1VBA 中运算符的分类VBA 中有很多运算符,大致可以分为四类,见下表。表 4-4 运算符分类种类算术运算符比较运算符连接运算符逻辑运算符功能用来进行数学计算的运算符用来进行比较的运算符用来合并字符串的运算符用来执行逻辑运算的运算符Excel VBA 程序开发自学通4.3.22020-2-26第 48页 /共 508页算术运算符算术运算符包括 7 个,其符号及功能见下表:表 4-5 算术运算符列表运算符^*/\Mod+-4.3.3功能求一个数字的某次方,如 A^B乘法运算除法运算对两个数作除法并返回一个整数求两数的余数加法运算减法运算比较运算符比较运算符包括 6 个,其符号及功能见下表:表 4-6 比较运算符列表符号<>==4.3.4功能小于小于或等于大于大于或等于等于不等于逻辑运算符逻辑运算符包括 6 个,其符号及功能见下表:表 4-7 逻辑运算符列表符号AndEqvImpNotOrXor功能用来对两个表达式进行逻辑连接用来对两个表达式进行逻辑等价运算用来对两个表达式进行逻辑蕴涵运算用来对表达式进行逻辑否定运算用来对两个表达式进行逻辑析取运算用来对两个表达式进行逻辑互斥或运算最常用的是 And、Not、Or 三种运算1.And 运算符And 运算符的运算规律见表 4-8 所示:表 4-8 And运算符运算规则Excel VBA 程序开发自学通2020-2-26如果条件一为TRUETRUETRUEFALSEFALSEFALSENullNullNull且条件二为TRUEFALSENullTRUEFALSENullTRUEFALSENull第 49页 /共 508页则结果为TRUEFALSENullFALSEFALSEFALSENullFALSENull实例:Sub AND 运算()Dim A, B, CA = 20B = 15C = 12MsgBox (A > B And B > C)End Sub' 返回 TRUE2.Or 运算的运算符Or 运算符的运算符规则见表 4-9 所示:表 4-9 Or运算符运算规则如果条件一为TRUETRUETRUEFALSEFALSEFALSENullNullNull且条件二为TRUEFALSENullTRUEFALSENullTRUEFALSENull则结果为TRUETRUETRUETRUEFALSENullTRUENullNull实例:Sub OR 运算()Dim A, BA = 10: B = 1MsgBox (A > 8 Or B > 8)'有一个条件满足,返回 TRUEMsgBox (A > 100 Or A < 10)' 两个条件都不满足,返回 FALSEEnd Sub]代码中半角单引号后面的汉字说明是程序的注释,程序执行时会自动忽略它。注释仅仅对代码的含义做说明,完全不影响代码的功能。对于注释的详细说明请参见本书第 9 章。3.Not 运算符Not 运算符的规则见表 4-10 所示表 4-10Not运算规则Excel VBA 程序开发自学通2020-2-26如果 条件 为TRUEFALSENull第 50页 /共 508页则 结果 为FALSETRUENull实例:Sub Or 运算()IF Not VBA.IsNumeric([A1]) ThenMsgBox "A1 不是数字"ElseMsgBox "A1 是数字或者为空"End IFEnd Sub4.3.5 运算符的优先顺序当一个表达式涉及到多个运算符时,就必须考虑运算符的优先顺序。在一个表达式中进行运算时,每一部分都会按预先确定的顺序进行计算求解,称这个顺序为运算符的优先顺序。在表达式中,当运算符不止一种时,要先处理算术运算符,接着处理比较运算符,然后再处理逻辑运算符。所有比较运算符的优先顺序都相同以出现位置为基准;也就是说,要按它们出现的顺序从左到右进行计算。而算术运算符和逻辑运算符则必须按下列优先顺序(由上至下)进行处理。可以用下表来表示三类运算符的优先级:表 4-11 运算符的优先级算术动算符指数运算:^负数:乘除法:*、/整除法:\求模运算:Mod加减法:+、字符连接:&比较动算符相等:=不等:小于:小于等于:=Like和Is逻辑运算符NotAndOrXorEqvImp用下例的一个实例,可能读者会更易明白运算优先级在 VBA 中的作用:Sub 运算优先级测试()MsgBox IIF(Not VBA.IsDate([a1]), [a1] + [b3] * 10, [a10] \ ([b10] + 10))End Sub读者可以在相关的单元格中录入数值,然后执行代码,根据结果可以看出代码涉及到的运算符的计算顺序。如果需要让表达式按自己的方式执行,而不是总以表 4-11 的顺序执行,如何可以实现呢?答案是肯定的——利用括号改变计算顺序。例如“MsgBox ([a1] + [b3]) / 100”语句中,就实现了先算加法再算除未予,从而改变了默认的优先级。Excel VBA 程序开发自学通2020-2-26第 51页 /共 508页4.4 简单的字符处理函数VBA 需要处理的字符通常有三类:文本字符串、数值和日期。本节介绍 VBA 中常用的字符串处理函数。4.4.1字符串处理函数功能介绍VBA 常用的字符处理的函数列表如下:表 4-12 常见字符处理函数列表作用Option CompareStrCompStrConvLcase、UcaseSpase、StringLenFormatLSet、RsetInStr、Left、Ltrim、Mid、Right、Rtrim、TrimSplit、Join关键字设置字符串比较规则比较两个字符串(字符相似判断)字符串类型转换大小写变换重复字符串计算字符串长度设置字符格式重排字符串处理字符串拆分与联接字符串特别需要指出的是:字符串进行比较时有不同的比较方式,通过 Option Compare可以指定这种方式。Option Compare 的作用是在模块级别中设置字符串比较时所用的默认比较方法。语法如下:语法Option Compare {Binary | Text | Database}在 Windows 系统中,有以下两种方式可选:Option Compare Binary:是根据字符的内部二进制表示而导出的一种排序顺序来进行字符串比较。它的排序方式大致如下:A < E < Z < a < b < e < z < 汉字即区分大小写,且大写字母小于小写字母,小写字母小于汉字Option Compare Text: 根据由系统区域确定的一种不区分大小写的文本排序级别来进行字符串比较。它的排序方式大致如下:(A=a) < (B=b) < (E=e) "A"End Sub本程序区分大小写比较,结果为 True。Option Compare TextSub 字母比较()MsgBox "a" > "A"Excel VBA 程序开发自学通2020-2-26第 52页 /共 508页End Sub本程序不区分大小写比较,结果为 False。4.4.2StrComp:字符相似比较StrComp:返回 Variant (Integer),为字符串比较的结果。语法:StrComp(string1, string2[, compare])表 4-13 StrComp函数比较结果如果string1string1string1string1小于 string2等于 string2大于 string2或 string 2为 NullStrComp 返回-101Null使用 StrComp 函数来比较两个字符串。如果第三个参数值为 1,字符串是以文本比较的方式进行比较;如果第三个参数值为 0 或是默认值,则以二进制比较的方式进行比较。Sub StrComp 运算()MsgBox StrComp("ABCD", "abcd", 1)MsgBox StrComp("ABCD", "abcd", 0)MsgBox StrComp("aBCDe", "AbcDE")End Sub4.4.3' 返回 0。' 返回 -1。' 返回 1。Strconv:字符串类型转换Strconv:返回按指定类型转换的 Variant (String)。语法:StrConv(string, conversion, LCID)其中第二参数表示转换类型,根据不同参数可以转换成 9 种类型的文本。其参数与功能对应关系如下:表 4-14 Strconv第二参数与实际转换类型表常数vbUpperCasevbLowerCasevbProperCasevbWidevbNarrovbKatakanavbHiraganavbUnicodevbFromUnicode值1234*8*16**32**64##说明将字符串文字转成大写将字符串文字转成小写将字符串中每个字的开头字母转成大写将字符串中单字节字符转成双字节字符将字符串中双字节字符转成单字节字符将字符串中平假名字符转成片假名字符将字符串中片假名字符转成平假名字符根据系统的缺省码页将字符串转成 Unicode将字符串由 Unicode 转成系统的缺省码页实例:Sub STRCONV 运算()MsgBox StrConv("English", vbUpperCase) & Chr(10) & StrConv("English",vbLowerCase) & Chr(10) & StrConv("English", vbProperCase) & Chr(10) &StrConv("English", vbWide)End SubExcel VBA 程序开发自学通2020-2-26第 53页 /共 508页以上代码可以将字符串“English”转换成四种效果,见图 4.5 所示:图 4.54.4.4STRCONV 函数转换字符串Format:格式化字符串Format:返回 Variant (String),利参数代码的字符串格式化成指定的样式。类似于于工作表函数 Text,但没有 Text 函数强大。语法:Format(expression[, format[, firstdayofweek[, firstweekofyear]]])本函数的 VBA 中运用非常广,最常用的地方是对日期或者时间进行格式化。例如获取现在相关的时间信息,可用以下代码:Sub 现在()MsgBox Format(Date, "yyyy 年 m 月 ") & Chr(10) & Format(Date, "AAA") & Chr(10) &Format(Now, "h") & "点钟", 64, "现在是"End Sub如果将代码中的“Format”替换成“WorksheetFunction.Text”可以得到完全相同的结果。图 4.6 利用 Format 提取日期和时间信息4.4.5LCase/ Ucase:大小写转换LCase/ Ucase:将英文字符串进行大写与小写互换语法:LCase(string)/ UCase(string)函数的功能是在大写与小写字母之间转换,虽然用法简单易懂,但功能和STRCONV 相去甚远,在工作中完全可使用 STRCONV 来替换这两个函数。实例如下:Sub 大小写转换()MsgBox LCase("HELLO WORLD!")MsgBox UCase("Hello World!")End SubExcel VBA 程序开发自学通4.4.62020-2-26第 54页 /共 508页String / space:重复字符1.String返回 Variant (String),其中包含指定长度重复字符的字符串。语法如下:String(number, character)2.Space返回特定数目空格的 Variant (String)。语法如下:Space(number)其中 String 函数可以对任意字符串重复 N 次出现,而 Space 则可以将空格重复 N次。所以 String 函数包括 Space 的功能。以下两例可以演示 String / space 函数的用法Sub 重复 N 次()MsgBox String(5, "*")' 返回 "*****"。MsgBox String(4, "中国")' 返回 "中中中中" 只重复左边一位MsgBox String(5, 60)' 返回 "<<<< 0 ThenIF Right(TextBox1.Text, 1) Like "[a-z]" Then Exit Sub Else Me.TextBox1 =Excel VBA 程序开发自学通2020-2-26第 57页 /共 508页Left(TextBox1.Text, Len(TextBox1.Text) - 1)End IFEnd Sub(5)光标定位于代码任意位置,按下快捷键【F5】运行窗体,在文字框中随意录入字符,可以发现除小写字母外任何字都无法录入,包括数字、标点与汉字。本例文件参见光盘:..\ 第四章\Like 用法.xlsm如果需要输入小写加大写字母,则代码中的“[a-z]”需要改为“[A-z]”。因为在二进制比较下,大写字母小于小写字母,那么“A-z”就包括了“A-Z”和“a-z”。而采用另一方式也可以完成同等功能:“[a-zA-Z]”。如果只允许 D 到 G 之间的字母以及 4-8 之间的数字,其它任何字符一概拒绝,那么 Like 语句可以改为:Right(TextBox1.Text, 1) Like "[4-8D-G]"。注意:Split、Join 两个字符处理函数全在数组方面,本章不作介绍。在本书第 13 章将进行详细解说。第五章VBA 数据类型与变量、常量VBA 代码的操作对象主要是数据。在编写代码时随时都会与各种类型的数据打Excel VBA 程序开发自学通2020-2-26第 58页 /共 508页交道,只有全面掌握数据类型,及代表各种数据的变量与常量才能高效地处理数据运算。本章要点: 数据类型 常量与变量5.1 数据类型数据类型就是一类数据的集合。它决定变量的占用空间及变量种类。使用合理的数据类型定义变量可以使程序具有更高的执行效率。5.1.1为什么要区分数据类型数据类型是指数据以何种方式储存在内存中。VBA 中的任何数据都有一种数据类型。数据类型可以由用户指定,称为显示声明;也可以由 VBA 程序自己选择,即隐式声明。然而自动分配的数据类型绝大部分时间准确,某些时候会产生错误,而且 VBA 分配数据类型是需要消耗内存的。也就是说在让 VBA 自行匹配数据类型的状态下,可以减少输入代码,却会牺牲程序的执行效率。所以在编写 VBA 程序时有必要定义所有变量的数据类型。VBA 中支持很多数据类型,这对新用户来户极难掌握。只有通过不断地编写、运用或者上论坛与 VBA 程序员在线交流,慢慢地积累经验。5.1.2认识 VBA 的数据类型VBA 中支持 10 多种数据类型。不同数据类型的差异主要体现在三方面:类型名称、占用内存空间大小和取值范围。表 5-1 中罗列了 VBA 所有 V 各种数据类型的空间与范围。表 5-1 VBA的数据类型数据类型ByteBooleanIntegerLong(长整型)Single(单精度浮点型)Double(双精度浮点型)Currency(变比整型)存储空间大小1 个字节2 个字节2 个字节4 个字节4 个字节Decimal14 个字节8 个字节8 个字节范围0 到 255True 或 False-32,768 到 32,767-2,147,483,648 到 2,147,483,647负数时从 -3.402823E38 到 -1.401298E-45;正数时从 1.401298E-45 到 3.402823E38负数时从 -1.79769313486231E308 到从 -922,337,203,685,477.5808 到922,337,203,685,477.5807没有小数点时为+/-79,228,162,514,264,337,593,543,950,335,而小数点右边有 28 位数时为+/-7.9228162514264337593543950335;最小的非Excel VBA 程序开发自学通DateObjectString(变长)String(定长)Variant(数字)Variant(字符)用户自定义2020-2-268 个字节4 个字节10 字节加字符串长度字符串长度16 个字节22 个字节加字符串长度所有元素所需数目第 59页 /共 508页零值为 +/-0.0000000000000000000000000001100 年1月1日到9999年12月31日任何 Object 引用0 到大约 20 亿1 到大约 65,400任何数字值,最大可达 Double 的范围与变长 String 有相同的范围每个元素的范围与它本身的数据类型的范围相同。从上表中分析得知,如果待计算的数据是单科成绩 0 到 100 之间,那么就应该使用 Byte 数据类型,用 Long 型虽然也可以正确执行,但它占用的内存是 Byte 的四倍,将使程序的效率降低;而使用 String 做为数据类型则会产生“类型不匹配”的错误,因为 String 用于文本,不适用于数值;如果非单科成绩,而是 10 科课程的总成绩,那么用 Byte 类型则会产生“溢出”的错误,因为 10 科成绩会大于 255,而 Byte 数据类型的取值仅仅在 0-255 之间。下面的实例将更有助于读者了解数据类型。(1)在工作表中录入图 5.1 所示数据;(2)进入 VBE 界面并插入新模块,在模块窗口录入以代码:Sub TEST()Dim SUMS As Long, CELL As Range, I As Byte, MYSTR As StringFor Each CELL In Range("A1:A10")IF VBA.IsNumeric(CELL) Then SUMS = SUMS + CELL Else MYSTR = MYSTR &CELLIF CELL = "" Then I = I + 1Next CELLDebug.Print "A1:A10 中有空白单元格" & I & "个"Debug.Print "A1:A10 中数据和为:"; SUMSDebug.Print "A1:A10 中文本为:"; MYSTREnd Sub(3)使用快捷键【Ctrl+G】显示立即窗口;(4)光标定位于代码中任意位置并按下快捷键【F5】,执行代码后在立即窗口将显示图 5.2 所示的结果:图 5.1 工作表数据图 5.2 统计结果从这个程序过程可以得出以下结论:(1)本序中使用了四个变量,分别对应四种数据类型:Excel VBA 程序开发自学通2020-2-26第 60页 /共 508页表 5-1 变量与数据类型变量SumsCellIMystr数据类型LongRangeByteString(2)本例中所使用的四种数据不可以互换,任何两个变量的数据类型都无法互换。每个变量都使用了合适的数据类型。用其中 Sums 变量的值较大,选中 Long 型可以储存-2,147,483,648 到 2,147,483,647 之间的数据;而 Cell 变量代表单元格,则必须使用 Range 对象类型;变量 I 在 1 到 10 之间变化,所以可以用最范围偏小的 Byte;而 Mystr 变量是文字串,那么只能用 String。(3)如果本例不声明所有变量的数据类型,程序执行效率会有所下降,尽管下降不多。5.1.3数据类型的声明与转换认识数据的类型、占用空间及定义数据类型的重要性后,就可以在程序中声明变量的数据类型了。声明数据类型使用 As 语句,在 As 之后直接附上数据类型即可,中间用空格分隔。例如:Dim a as Byte数据类型不区分大小写,不管用“BYTE”、“Byte”还是“byte”都可以,但 VBA会自动将录入数据类型转换成首字母大写、其它字符小写的格式。如果不指定数据类型,则 VBA 自动将变量指定为变体型 Variant。即以下两句代码具有相同的作用:Dim iDim I as Variant变量的数据类型根据需要也可以转换,即在声明数据类型时若使用了占用内存较大的数据类型,而在某个阶段所获取的值却很小,当用这个很小的数据去参数计算时,就有必要将该数据转换成占用内存更小的数据类型。例如:Sub test()Dim 成绩 As Long成绩 = 80'中间代码,利用变量“成绩”参与各种运算End Sub在以上代码,变量“成绩”的类型是占用 4 字节的 Long 型,但事实上它的值为80,那么利用变量“成绩”参与后续的各种运算将浪费内存。而将其转换成 Byte 型再参与运算,则可以提速,体现出类型转换之现实意义。从另一个角度考虑,因不同的数据类型对小数处理的精度不同,那么数据转换后可以实现更符合需求的精度。可用于数据类型转换的函数见表 5-2 所示:表 5-2 数据类型转换函数列表函数返回类型expression 参数范围Excel VBA 程序开发自学通CBoolCByteCCurCDateCDblBooleanByteCurrencyDateDoubleCDecDecimalCIntCLngCSngIntegerLongSingleCStrCVarStringVariant2020-2-26第 61页 /共 508页任何有效的字符串或数值表达式0 至 255-922,337,203,685,477.5808 至922,337,203,685,477.5807任何有效的日期表达式负数从 -1.79769313486231E308 至 -4.94065645841247E-324;正数从4.94065645841247E-324 至 1.79769313486232E308零变比数值,即无小数位数值,为+/-79,228,162,514,264,337,593,543,950,335。对于 28 位小数的数值,范围则为+/-7.9228162514264337593543950335;最小的可能非零值是0.0000000000000000000000000001。-32,768 至 32,767,小数部分四舍五入-2,147,483,648 至 2,147,483,647,小数部分四舍五入负数为 -3.402823E38 至 -1.401298E-45;正数为 1.401298E-45 至3.402823E38依据 expression 参数返回 Cstr若为数值,则范围与 Double 相同;若不为数值,则范围与 String 相同除上面的转换函数外,还有一个用于识别数据类型的函数:TypeName。功能:返回一个 String,提供有关变量的信息。语法:TypeName (varname)如判断一个数据或者变量是何数据类型,可以用以下语句:Msgbox Typename(10)——返回“Integer”,VBA 自动分配的类型Msgbox Typename(65536) ——返回“Long”Msgbox Typename([a1]) ——返回“Range”Msgbox Typename(Mystr) ——在未对变量赋值的状态下返回“Empty”如果利用表 5-2 中的函数对变量进行转换,那么 Typename 返回值会相应变化,变量本身的值也可能变化。Sub 类型转换()Dim funds As Doublefunds = 80.42454MsgBox "类型:" & TypeName(funds) & " 值:" & fundsMsgBox "类型:" & TypeName(CBool(funds)) & " 值:" & CBool(funds)MsgBox "类型:" & TypeName(CByte(funds)) & " 值:" & CByte(funds)End Sub以上程序中变量的初始值是 80.42454,在经过类型转换后其类型与值变化如下:图 5.3Double 型图 5.4 Boolean 型图 5.5Byte 型除转换函数外,还有一种特殊的转换方式,可以在文本与数值之间转换。例如以下代码:Sub 不同状态下的数据类型()Dim 月份 As String, 学号 As Integer月份 = 10学号 = 354MsgBox TypeName(月份 * 1)MsgBox TypeName(学号 & "")Excel VBA 程序开发自学通2020-2-26第 62页 /共 508页End Sub代码中变量“月份”声明为文本类型 String,“学号”声明为数字 Integer,但文本型数字经过“*1”后可以转换成数值,其数据类型为 Double;而数字型的学号连接一个空文本后自然也就成了 String 型。5.2常量与变量变量与常量是 VBA 程序中不可或缺的要素。灵活运用变量与常量,可使代编写更简单、高效。5.2.1常量的定义与用途常量也称常数,在帮助中,Excel 对常量的定义为“执行程序时保持常数值的命名项目。常数可以是字符串、数值、另一常数、任何( 除乘幂与 Is 之外的) 算术运算符或逻辑运算符的组合。”如果更通俗地讲,常量就是在程序执行过程中永远不变的量。“你我他”是一个常量,“Asc”是一个常量,128 也是一个常量。常量的用途主要体现在两方面:1.简化输入在某个程序中需要多次使用一个数值 3.1415926 时,如果每次都录入这个长长的数值,效率会降低,同时也有录入错误的潜在危险,例如小了一位数,或者多了一个小数点。通常的解决办法是定义一个常量“P”,对常量赋值为 3.1415926,后续的操作时直接用 P 参与运算。结果完全相同,但在录入时却轻松许多。2.方便识别仍是前一个实例来说明,在程序中大量运用数据 3.1415926,而程序的终端用户却不一定知道这数据是代表什么。如果我们在代码中定义一个常量“圆周率”,并对其赋值为 3.1415926,岂非可以顾名思义?5.2.2常量的声明方式声明常量可以使用 Const 语句来完成。Const 的语法如下:[Public | Private] Const constname [As type] = expression其中 Public 是可选的,该关键字用于在模块级别中声明在所有模块中对所有过程都可以使用的常量,在过程中声明常不能使用 Public;Private 也是可选的,该关键字用于在模块级声明只能在包含该声明的模块中使用的常数,不能在过程中使用;Constname 是必需的参数。用于指明常数的名称;Excel VBA 程序开发自学通2020-2-26第 63页 /共 508页Type 参数是可选的,表示常数的数据类型。Expression 是必需的参数。用于指定文字、其它常数,或由除 Is 之外的任意算术操作符和逻辑操作符所构成的任意组合。例如声明一个常量可以用以下语句:Const 圆周率 As Double = 3.1415926Const 圆周率 = 3.1415926Public Const 圆周率 As Double = 3.1415926第一句声明语句指定了常量名称和常量类型,第二句未指定常量类型;第三句使用了 Public,必须在模块顶部使用,而不能在过程是使用。图 5.6 即为错误声明常量的提示。图 5.6在模块中使用 Public 时的错误提示正确的申明方式为:Public Const 圆周率 As Double = 3.1415926Sub test()MsgBox 圆周率End Sub5.2.3常量的命名规则常量有两类:内置常量和用户定义常量。其中内置常量是 Excel 定义的,用户直接调有即可。例如:xlLandscape 和 xlPortrait。这两个常分别代表 2 和 1,用于设定页面的方向是横向还是纵向,在 VBA 中可以调用这两个常量,也可以直接用 2 和 1。还有 Msgbox 的参数“vbDefaultButton1”、“vbCritical”等等也是内置常量。用户定义的常量则需要按一定规则来定义后才可以使用。常量命名规则如下:(1)第一个字符必须使用英文字母或者汉字;(2)不能在常量名称中使用空格、句点(.)、惊叹号(!)、或 @、&、$,# 等字符;(3)名称的长度不可以超过 255 个字符;(4)常量名称不能与其自身的 Function 过程、语句以及方法的名称相同。如果常量与内置函数同名,那么在调用内置函数时可将内置函数、语句或方法的名称之前加上关联的类型库的名称。例如,如果有一个名为 Mid 的变量,则多方面要使用“VBA.Mid”来调用 Mid 函数,否则只能调用常量;(5)不能在范围的相同层次中使用重复的名称,但不同级别的代码中却可以使用。例如在模块中使用一个“Arr”的公有常量,在一个过程中也可以同时再定义一Excel VBA 程序开发自学通2020-2-26第 64页 /共 508页个名为“Arr”的私有常量。(6)不能与 VBA 的保留字一致。例如 Dim 和 Sub、End 等等;(7)常量名称不区分字母大小写;(8)在常量名称中间可以使用标点符,也可以使用分隔符_来区分多个单词。例如:Coloer_Count 或者 Sum_byte 等等。下面例举一些不符合要求的常量声明方式:Const 12st as ByteConst ?asc as IntegerConst dim As Byte = 11Const sub as StringConst "ABC" As String5.2.4变量的定义与用途变量是一个已经命名的存储位置,它包含了程序每个执行阶段所修改的数据。每一变量都有变量名,在其范围内可唯一识别。更通俗地说,变量就是在程序执行可中以随时变化的量。变量和常量一样,也具有简化输入和方便识别两个优点,同时变量有一个常量不具备的优点:储存不确定的数据。特别在循环语句中,待处理的数值随时会变量变化,常量不足以满足需求,而只能借助常量。例如:Sub 显示用户名()User_name = Application.InputBox("请输入您的姓名", "姓名", , , , , , 2)MsgBox User_nameEnd Sub在执行在以上代码时,会弹出一个对话框让用户录入姓名,然后弹出一个与录入的姓名一致的消息框。见图 5.7 和 5.8 所示。图 5.7 录入姓名图 5.8 反馈信息代码中 User_name 就是一个变量,它代表一个不确定的值。因为没有人知道终端用户会录入什么字符,而利用一个变量来取代它参与其它的所有运算或者显示是最高效的作法。从下面的实例,读者可以得到一个真实感受:声明变量对效率的大幅提升:Sub 测试一()Dim tim As Long, x As Integer, y As Integer, z As Integertim = TimerFor x = 1 To 10000For y = 1 To 10000z=x+yNext y, xExcel VBA 程序开发自学通2020-2-26第 65页 /共 508页MsgBox "时间:" & (Timer - tim)End SubSub 测试二()tim = TimerFor x = 1 To 10000For y = 1 To 10000z=x+yNext y, xMsgBox "时间:" & (Timer - tim)End Sub分别执行以上两代个码,可以现发现“测试二“的执行时间是“测试一”的两到三倍,而它们达成的目的相同,区别仅仅是“是否声明变量类型”。5.2.5变量的类型与声明变量的类型与常量一致,参见表 5-1 VBA 的数据类型。在代码编写中,变量可以声明再使用,也可以不声明就使用。对于初学者来说,掌握所有变量类型及记住其占用空间是有困难的,所以可能部分读者在初学时会不声明变量,让 VBA 自动分配。然而这种做法虽方便,却是牺牲了程序的性能。如果立志成为一名优秀的程序员,建议勾选“选项”对话框中的“要求声明变量”,而且在声明变量时必须指明其数据类型。声明变量有四种方式:Dim、Public、Private、Static。其中最常用的是 Dim。其语法如下:Dim [WithEvents] varname[([subscripts])] [As [New] type] [, [WithEvents]varname[([subscripts])] [As [New] type]]各关键的含义见表 5-3:表 5-3 Dim参数说明部分WithEventsvarnamesubscriptsNewtype描述可选的。声明varname是一个用来响应由 ActiveX 对象触发的事件的对象变量。只有在类模块中才是合法的必需的。变量的名称;遵循标准的变量命名约定可选的。数组变量的维数;最多可以定义 60 维的多维数组可选的。可隐式地创建对象的关键字。如果使用 New 来声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用Set 语句来给该对象引用赋值。New 关键字不能与WithEvents一起使用可选的。变量的数据类型。所声明的每个变量都要一个单独的 Astype 子句下面例举一些常见的变量声明方式:Dim nameDim a As ByteDim 姓名 As StringDim WithEvents xlApp As ApplicationDim 声明变量时允许同时声明多个,也允许部分指定数据类型、部分不指定数据类型。在同一行中只需要使用一个 DIM 语句。例如:Dim a, b, c——声明了三个变体型(Variant)变量Excel VBA 程序开发自学通2020-2-26第 66页 /共 508页Dim a As Byte, b, c, d As String——声明了四个变量,其中有一个 Byte 型,一个String 型。下面是错误的声明方式:Dim a as String,b,dim c as Variant以上代码使用了两个 Dim 在同行中。Dim a As String, bc As String以上代码第二行未使用 Dim 语句。另外一点,变量与常量一样也不能声明为 Excel 的保留字。包括 Sub、End、Next和 End 等等。如“dim next as String”是一个不合法的声明。5.2.5区分静态变量与动态变量动态变量是指每次被过程调用时均重新初始化的变量,而静态变量在初始化后,下一次调用时保留上次的值的变量。静态变量和动态变量的区别由 Static 和 Dim 等不同声明方式引起的。Static 语句用于声明静态变量。在模块的代码开始运行后,使用 Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用Static 语句显式声明只在该过程内可见、但具有与包含该过程定义的模块相同生命期的变量。Static 语句声明变量和 Dim 语句的语法相同,不同处在于变量的值。Static 语句声明的变量会一直保持其值,而 Dim 语句声明的变量会在过程结束时释放其值。下面的实例可以比较两种方式声明变量的区别:Sub 动态与静态()Dim A As ByteStatic B As ByteA=A+1B=B+1Debug.Print "A 等于" & ADebug.Print "B 等于" & BEnd Sub连续执行三次主面的代码,在立即窗口中显示的结果见图 5.9 所示。图 5.9 动态变量与静态变量的区别从上面的代码可以看出,静态变量在过程执行完毕后变量的值一直保存在内存中;而动态变量却在程序完成时归零或者返回空值。Excel VBA 程序开发自学通5.2.62020-2-26第 67页 /共 508页变量的作用域与生命周期变量的作用域是指允许在什么地方调用变量。变量分为公有变量和私有变量,也称模块级变量和过程级变量。这种差异取决于声明方式。其中私有变量的作用域是当前过程,而公有变量允许多个过程调用同一变量。具体表现如下:表 5-4 变量的作用域作用域当前过程当前模块所有模块变量的声明方式在本过程中利用Dim或者Static声明在本模块内第一个过程前用Dim或者Private声明在模块内第一个过程前用Public声明现举例演示:Dim TEMP As ByteSub A()TEMP = 10MsgBox TEMPEnd SubSub B()TEMP = TEMP + 10MsgBox TEMPEnd Sub在一个模块代码窗口中,在 Sub 过程之前声明了一个变量 temp,那么这个变量是模块级变量。它可以在该模块的任何过程中调用。当用户运行第一个过程后,temp 已赋值为 10,再执行第二个过程时,temp 的结果等于 20。这是因为第一个过程改变了本模块公有变量 temp 的值,而第二个过程调用变量 temp 时,仍保留了 temp 在第一个过程中的值。再看看一个过程级别的变量用法?Sub A()Dim TEMP As ByteTEMP = 10MsgBox TEMPEnd SubSub B()Dim TEMP As ByteTEMP = TEMP + 10MsgBox TEMPEnd Sub这两个过程都声明了变量 temp,但两个变量是不相干的。第一个过程执行后 temp值为 10,第二个过程序执行时,变量 temp 将会重新初始化,而不会保留过程 A 中 temp的值。除 Dim 语句外,还可以使用 Private 语句来声明变量。Private 语句:在模块级别中使用,用于声明私有变量及分配存储空间。使用 Private 语句来声明变量,可以使变量在本模块中使用,和前面 Dim 语句声明模块级变量一致。声明变量还有一种语句: Public 语句Excel VBA 程序开发自学通2020-2-26第 68页 /共 508页Public 语句:在模块级别中使用,用于声明公用变量和分配存储空间。Public 语句在所有应用程序的所有没有使用 Option Private Module 的模块的任何过程中都是可用的;若该模块使用了 Option Private Module,则该变量只是在其所属工程中是公用的。举一个实例:在模块 1 中输入以下代码:Public TEMP As ByteSub A()TEMP = 10MsgBox TEMPEnd Sub在模块 2 中输入以下代码:Sub B()TEMP = TEMP + 10MsgBox TEMPEnd Sub当执行模块 1 中的过程 a 后,temp 的值为 10;而执行模块 2 中的过程 b 后,temp 的值为 20,因为它调用的是工程中的公用变量,当模块 1 中的过程改变了变量 temp 的值后,执行过程 b 时保留了 temp 的值。注意:Dim 和 Private 和 Public 语句声明的变量都是动态变量。只有 Static 语句声明的变量是静态变量。变量保留其值的这段时间,称为生存周期。变量的值在整个生存周期可能一直接在产生改变,也可以一直保持相同值,但它在生命周期中一定保留着一个值。只有当变量失去了范围之后,它才返回空或者 0 或者 Nothing。一个变量在程序开始时、赋值前都会被初始化。不同数据类型的变量初始化时为不同值: 数值变量:初始化成 0; 变长字符串:被初始化成零长度的字符串 (""),也称空文本; 定长字符串:会被填满 Chr(0); 变体型变量:则会被初始化成 Empty; 用户定义类型:每一个元素变量会被当成个别变量来做初始化; 对象变量:被设置成 Nothing,直到利用 Set 语句对它指定一个对象引用。生存周期和作者域是相关联的,对于过程级么有变量,在过程结束后,变量的生存周期即已终止。而公有变量则在关闭 Excel 后才会终止,除非人工消毁变量。5.2.7认识对象变量对象变量也是变量的一种,但却有它独有的特殊性。对象变量是代表一个对象,不是一个内存中的一个值。对象包括工作表对象、工作表簿对象、单元格对象,甚至 Excel 应用程序对象。对象变量的声明方式与其它变量一致,不再赘述。但变量的赋值方式不同: Let 方法:用于对象以外的变量赋值,是可选参数 Set 方法:对对象变量进行赋值,是必选参数例如对非对象变量赋值,可以用:Excel VBA 程序开发自学通2020-2-26第 69页 /共 508页Let A=10A=10但如果变量 A 是单元格对象时,则需要用以下方式赋值:Set A = Range(“A10”)Set A = [A10]对象变量在初始化时值为 Nothing,可以理解为“什么也没有”。而在变量赋值后它就代表了一个对象,如 A1 单元格,一个工作表或者一个应用程序。在程序结束前可以销毁这个对象变量,方法如下:Set a = Nothing程序中使用对象变量有两个优点:简化多级对象的录入和提升程序的执行效率。能过下例程序的比较也可以证明对象变量的优势:Sub 设置 A10 的字体()ActiveWorkbook.Sheets("Sheet2").Range("A10").Font.Name = "黑体"ActiveWorkbook.Sheets("Sheet2").Range("A10").Font.ColorIndex = 3ActiveWorkbook.Sheets("Sheet2").Range("A10").Interior.ColorIndex = 5End SubSub 设置 A10 的字体二()Dim rng As RangeSet rng = ActiveWorkbook.Sheets("Sheet2").Range("A10")rng.Font.Name = "黑体"rng.Font.ColorIndex = 3rng.Interior.ColorIndex = 5End Sub以上第一个程序在输入时费时很多,而第二个程序中利用一个简单的变量 Rng 替代“ActiveWorkbook.Sheets("Sheet2").Range("A10")”后直接参与后续的所有操作,有助于快速录入代码,且避免潜在的录入错误。另一个优点是程序二中执行效率较第一个更高。如果一定要测试它们在效率上差异多少,可以将程序循环 10000 次再做比较,便于直观的分辨时间差异。Sub 设置 A10 的字体()tim = TimerFor i = 1 To 10000ActiveWorkbook.Sheets("Sheet2").Range("A10").Font.Name = "黑体"ActiveWorkbook.Sheets("Sheet2").Range("A10").Font.ColorIndex = 3ActiveWorkbook.Sheets("Sheet2").Range("A10").Interior.ColorIndex = 5NextMsgBox Timer - timEnd SubSub 设置 A10 的字体二()tim = TimerDim rng As RangeSet rng = ActiveWorkbook.Sheets("Sheet2").Range("A10")For i = 1 To 10000rng.Font.Name = "黑体"rng.Font.ColorIndex = 3rng.Interior.ColorIndex = 5NextMsgBox Timer - timEnd Sub分别执行以上两段程序,可以得到两个执行时间。它们之间差大概在 3 秒左右。Excel VBA 程序开发自学通5.2.82020-2-26第 70页 /共 508页认识数组变量VBA 中也支持数组变量。数组是一组具有相同性质的数据的集合,在 VBA 中使用数组可以大的提升程序执行效率。数组的声明方式和其它的变量是一样的,它可以使用 Dim、Static、Private 或Public 语句来声明,但它们的不同点在于数组通常需要指定大小。数组的大小被指定后,则它是个固定大小数组,若程序运行时数组的大小可以被改变,则它是个动态数组。数组的具体应用请参阅本书第 13 章。第六章认识 VBA 过程及开发自定义函数VBA 的主体结构就是过程。VBA 包括子过程、函数过程和属性过程三种,本书主要介绍子过程(也称 Sub 过程)和函数过程(也称 Function 过程)。本章要点: 认识过程 SUB 过程 Function 过程 关于过程参数 开发自定义函数Excel VBA 程序开发自学通2020-2-26第 71页 /共 508页编写函数帮助6.1 认识过程VBA 中每一个程序都包含过程。录制的宏是一个过程,一个自定义函数也是一个过程。掌握好单个过程的编写与思路,就可以组合成一个大中型插件或者专业程序。6.1.1过程的分类与调用方式过程主要分为三类:子过程、函数过程和属性过程。这三类过程分别为以下三种格式Sub 子过程()……End SubFunction 函数过程(rng As Range)……End FunctionProperty Get 属性过程() As Variant……End PropertyProperty Let 属性过程(ByVal vNewValue As Variant)……End Property本书主要讲述 Sub 子过程和 Function 函数过程的开发。Sub 过程是 VBA 是应用最广的过程,录制宏所产生的过程就是 Sub 子过程。子过程的执行方式包括五类:1.【Alt+F8】执行如果在工作表命令窗口、Thisworkbook 命令窗口或者标准模块窗口中存在 Sub 过程,那么在工作表界面可以通过快捷键【Alt+F8】来执行命令。假设在 VBE 界面中 Sheet1 代码窗口中有一个 Sub 过程“汇总”,在模块 1 中有一个名为“新建菜单”的 Sub 过程,那么通过快捷键【Alt+F8】打开“宏”对话框后,将在对话框中产生两个可执行程序名,其中工作表命令窗口的 Sub 过程会连同工作表名一起出现在宏名列表中,而模块中的过程则仅仅列出过程名。用户选择目标程序并单击【执行】即可启动 Sub 子过程。2.快捷键执行Sub 过程可以与某个快捷键进行关联,在后续的使用中就可以利用这个快捷键来调用对应的过程。设置 Sub 过程的快捷键主要有两种方式:利用宏对话置及用 VBA 代码指定。后者在本书其它章节将会讲述,在此演示一下“宏”对话框设置宏的快捷键的方法:假设 VB 工程中仍然有以上两个名为“汇总”和“新建菜单”的 Sub 过程,在工作表界面中按下快捷键【Alt+F8】调出图 6.1 所示对话框,然后选择“新建菜单”,并Excel VBA 程序开发自学通2020-2-26第 72页 /共 508页单击“选项”按钮,在弹出的“宏选项”对话框中指定快捷键,见图 6.2 所示。图片设计宏程序“新建菜单”的快捷键是【Ctrl+q】。图 6.1 “宏”对话框图 6.2 设置 Sub 过程的快捷键3.按钮执行可以工作表中建一个按钮,并将按钮与 Sub 过程关联,从而实现单击按钮执行程序。将按钮关联到 Sub 过程的步骤为:(1)单击菜单【开发工具】\【表单控件】\【按钮】;(2)在工作表中按下左键并向右下方拖动,从而绘制一个控件按钮;(3)在弹出的对话框中选择“新建菜单”,见图 6.3 所示;(4)返回工作表后即可单击名为“按钮 1”的按钮来执行程序“新建菜单”。Excel VBA 程序开发自学通2020-2-26第 73页 /共 508页图 6.3 关联过程与按钮4.菜单调用最常见的是编写一个自定义菜单或者工具条来调用 Sub 过程。菜单与工具条的设计方法参见本书 22 章及 23 章。5.事件引发对于部分需要自启动的程序,通常利用事件事引发,不需要人工干预。例如工作簿开启时就自动执行某程序,或者关掉窗体、鼠标移过窗体时执行某程序…….对于事件过程的运用参见本书 12 章。6.工作表中使用公式调用Function 过程即自定义函数,可以像使用内置的工作表函数一样在公式中使用。调用 Function 过程的步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块中录入以代码:Function 成绩(rng)成绩 = IIF(rng >= 60, "及格", "不及格")End Function(3)返回工作表中,在 A1 输入数值 50,在 B1 输入公式:=成绩(A1)可以发现公式可以像内置函数一样运行,它返回“不及格”,正是期望的结果。6.1.2插入过程的方式编写过程时可以手工录入代码,也可以让利用 VBA 提供的列表自动产生程序外壳。一个 Sub 过程分为程序外壳部分和主体部分。例如:Excel VBA 程序开发自学通2020-2-26第 74页 /共 508页图 6.4 Sub 过程的外壳与主体部分示意图其中外壳部分可手工以录入,也可以利用 VBE 提供的方式完成。1.非事件过程对于非事件的 Sub 过程,VBA 提供了一个专用窗体来选择性录入过程的外壳。具体步骤如下:(1)在 VBE 界面中单击菜单【插入】\【模块】;(2)单击菜单【插入】\【过程】打开“添加过程”对话框;(3)在“中称”框中录入“汇总”,并将“类型”选择“子过程”,将“范围”设为“私有”,见图 6.5 所示。然后单击“确定”按钮。执行以上程序后在模块中可以看到产生的代码为:Private Sub 汇总()End Sub如果是 Function 函数过程,也可以按照上述方法录入过程的外壳。2.事件类过程VBA 支持很多类事件,在部分事件的代码都需要参数。而这些参数是很难记忆的,包括所有 VBA 专业程序员。为了快速且准确地录入事件类过程,可以通过 VBE提供的对象与过程窗口的下拉列表完成。例如输入工作表 SelectionChange 事件的过程,方法如下:(1)使用快捷键【Alt+F11】进入 VBE 界面,并用快捷键【Ctrl+G】打开工程资源管理器窗口;(2)双击 Sheet1 或者其它需要录入工作表事件的工作表名;(3)从对象窗口的下拉框中选择“Worksheet”,代码窗口默认产生以下代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)End SubExcel VBA 程序开发自学通2020-2-26图 6.5 添加 Sub 过程外壳图第 75页 /共 508页6.6从下拉列表选择对象因为 VBA 默认状态下就是弹出“Worksheet_SelectionChange”事件的代码,所以当选择对象为“Worksheet”后就产生了需要的代码。如果需要录入“Worksheet_Change”事件的代码,则需要在选择对象“Worksheet”后,再选过程“Change”,然后将产生的“Worksheet_SelectionChange”事件的代码删除,仅保留以下代码:Private Sub Worksheet_Change(ByVal Target As Range)End Sub对于此类包含参数的事件过程,应该尽量通过对象与过程窗口的下拉列表产生代码的方式,手工录入是容易产生误差。在用户窗体中很多很多事件也支持参数,而且有多个参数,通常也需要从列表中选择对象与过程的方式来录入代码。例如在窗体中录入鼠标移过事件的过程,步骤如下:(1)单击菜单【插入】\【用户窗体】;(2)使用快捷键【Ctrl+G】显示工程资源管理器,并在 UserForm1(或者别的名称)上单击右键,选择菜单【查看代码】;(3)从对象窗口选择“UserForm1”,此时默产生“UserForm_Click”事件的代码;再从过程窗口选择“MouseMove”,代码窗口中将产生以下代码:Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByValX As Single, ByVal Y As Single)End Sub(4)删除“UserForm_Click”事件的代码。6.1.3过程的命名规则过程的命名与变量的命名规则一致。但要补充一点是:过程名可以和本过程的私有变量同名,但却不能和公有变量同名。例如:Sub 身份证()Dim 身份证 As StringExcel VBA 程序开发自学通2020-2-26第 76页 /共 508页身份证 = [a1].TextEnd Sub以上代码中过程与变量同名,但这是允许的。Dim 身份证 As StringSub 身份证()身份证 = [a1].TextEnd Sub这段代码却是非法的,只要运行程序就会弹出编译错误。为了避免错误及便于识别,既使本过程的私有变量也尽量保持与过程名不相同。6.2编写 SUB 过程本节开始了解关于 Sub 过程的基本概念,以及编写简单的 Sub 过程。6.2.1 SUB 过程的语法解析Sub 过程即利用 Sub 语句声明的过程。所以宏录制器产生的过程全是 Sub 过程,无法通过录制宏产生 Function 过程或者属性过程。Sub 语句声明过程的语法如下:Private | Public | Friend] [Static] Sub name [(arglist)][statements][Exit Sub][statements]End Sub其中各参数的详细功能见表 6-1 所示:表 6-1参数部分PublicPrivateFriendStaticnameargliststatementsSub语句参数详解功能解释可选的。表示所有模块的所有其它过程都可访问这个 Sub 过程。 如果在包含 Option Private 的模块中使用,则这个过程在该工程外是不可使用的可选的。表示只有在包含其声明的模块中的其它过程可以访问该 Sub 过程可选的。只能在类模块中使用。表示该 Sub 过程在整个工程中都是可见的,但对对象实例的控制者是不可见的可选的。表示在调用之间保留 Sub 过程的局部变量的值。Static 属性对在Sub 外声明的变量不会产生影响,即使过程中也使用了这些变量必需的。Sub 的名称;遵循标准的变量命名约定可选的。代表在调用时要传递给 Sub 过程的参数的变量列表。多个变量则用逗号隔开可选的。Sub 过程中所执行的任何语句组Sub 过程与所有变量一样,也区分公有和私有,而在说法上稍有区别。过程分模块级过程和工程级过程。1.模块级过程模块级过程即只能在当前模块调用的过程,它的特征有三处:Excel VBA 程序开发自学通2020-2-26第 77页 /共 508页(1)声明 Sub 过程前使用 Private;(2)只有当前过程可以调用,例如在“模块 1”中有以下代码:Private Sub 过程一()MsgBox 123End SubPrivate Sub 过程二()Call 过程一End Sub执行过程二时可以调用过程一,但如果过程二存放于“模块 2”中,则将弹出“子过程未定义”的错误提示。(3)不出现在“宏”对话框中。即使用快捷键【Alt+F8】所打开对话框中无法查看到当前过程的名称列表。如果是 Function 过程,则无法在函数向导中查看到函数名。提示:所有事件的代码都是过程级的,默认状态下只有在当前过程可以调用。2.工程级过程工程级过程是指在当前工程中任意地方都可以随意调用的过程。它的特征刚好与模块级过程相反:在“Sub”语句前置标识符“Public”、非当前过程可以调用,可以出现在“宏”对话框中。如果一个过程没有使用“Public”和“Private”标识,则默认为公有过程,任何模块或者窗体中都可以调用。Sub 过程也支持参数,其参数的用法与 Function 过程的参数用法一致,本小节不详述,请参阅要书“6.4 关于过程参数”。3.中途退出程序的多种方法与分别Sub 过程可以在程序中间任意位置退出程序,通常是设定的个件条件,当满足条件时使用“Exit Sub”来退出程序。当程序退出后,后面的代码不再执行。也可以使用“End”来退出程序。“End”和“Exit Sub”在使用中有相同处,也有明显的分别。相同处是都可以中途终止程序的运行,不是处则有以下两点: 是否释放公有变量从下三段代码可以体现“End”和“Exit Sub”的差异:Dim x As LongSub A()x = 888Exit SubEnd SubSub B()x = 888EndEnd SubSub C()MsgBox xEnd Sub代码中 X 是公有变量,当执行过程 A 后执行过程 C,那么变量 X 的值为 888,表示 X 变量的值在过程中并没有释放,“Exit Sub”仅仅退出程序执行,公有变量的值保护不变。Excel VBA 程序开发自学通2020-2-26第 78页 /共 508页如果执行过程B再执行过程C,那么X的值则为0,说过在过程B中的“End”已经释放变量 X 的值。 是否终止所有程序仍然用三种个过程来演示“End”和“Exit Sub”的差异:Sub A()Call BMsgBox "终止"End SubSub B()Exit SubEnd SubSub C()EndEnd Sub执行程序 A 的结果是弹出对话框“终止”,而将过程 A 中的“Call B”修改为“CallC”,那么什么反应也没有。也就是“Exit Sub”是退出它所在的程序,而“End”则中止所有程序,包括调用它的程序。如果在窗体代码中,“Exit Sub”仅仅退出事件,而“End”则退出事件后关掉窗体,窗体中声明的所有变量全部释放。6.2.2 Sub 过程的执行流程如果录用宏并执行宏,可以看出宏代码的执行流程永远是从上到下。可以使用调试功能来查看流程。例如执行以下代码:Sub 设置 A1 单元格()Range("A1").SelectRange("A1") = "中华人民共和国"Range("A1").Interior.Color = 65535Range("A1").Font.ColorIndex = 3Range("A1").Borders.LineStyle = xlContinuousRange("A1").Font.Name = "黑体"Range("A1").Font.Size = 20Range("A1").EntireColumn.AutoFitEnd Sub将 VBE 窗口缩小,使自己能同时看到代码及 A1 单元格的情况下再按下快捷键【F8】,从而进入逐句调试阶段。注意:在 VBE 中使用【F8】键表示调试语代码句,每按下一次【F8】即执行一句,忽略变量与常量的声明语句,直到“Exit Sub”或者“End”、“End Sub”。在编写代码时非常有用,可以借助它检查代码的准备性,同时也可以查看程序间的跳转是否正常(当有标签设置和嵌套调用的时候)。当按下调试键【F8】时当前执行的语法呈黄色显示,再次按下【F8】时则下一句呈黄色显示,而操作对象 A1 则对应产生变化。图 6.7 中已执行到第四句,所以 A2单元格同步后的状态就是录入“中华人民共和国”后并设置了背景色为黄色。Excel VBA 程序开发自学通2020-2-26第 79页 /共 508页图 6.7 逐步执行代码当继续通过【F8】键执行完成的代码后,可以得出结论:所有录制的宏和未特别指定程序跳转的 VBA 代码总是从上至下的流程逐句执行。那么是否有例外呢?通常在三种情况下会例:1.使用冒号使一行执行多句代码VBA 中允许借助冒号将多句代码写在同一行执行。对同行中的代码按从左向右的顺序执行。例如:Sub 设置 A1 单元格()Range("A1") = "中华人民共和国": Range("A1").Interior.Color = 65535Range("A1").Font.ColorIndex = 3: Range("A1").Font.Size = 20Range("A1").EntireColumn.AutoFitEnd Sub以上代码在借助冒号将四行代码缩至两行,但执行过程仍然会为四步。对于同行中有多句代码时,按从左向右的顺序执行。那么读者一定可以想到,使用冒号和不使冒号的执行结果岂非完全一致?仅仅改变了行数?答案是“有时一致,有时不一致”。如果以上的代码按如下方式编写,那么结果完全一致:Sub 设置 A1 单元格()Range("A1") = "中华人民共和国"Range("A1").Interior.Color = 65535Range("A1").Font.ColorIndex = 3Range("A1").Font.Size = 20Range("A1").EntireColumn.AutoFitEnd Sub而在下面的代码中,使用冒号后却可以得到完全不同的结果:Sub 判断是否及格 1()IF [B2] >= 60 Then [C3] = "及极": Exit SubIF [B3] >= 60 Then [C3] = "及格"End SubSub 标示最大值 2()Excel VBA 程序开发自学通2020-2-26第 80页 /共 508页IF [B2] >= 60 Then [C3] = "及极"Exit SubIF [B3] >= 60 Then [C3] = "及格"End Sub假设工作表中有图 6.8 所示数据,执行过程“判断是否及格 1”时,C3 单元格将出现“及格”;而执行过程“判断是否及格 2”时则无任何反应。也就是说“Exit Sub”语句与 IF 同行时,只有单元格 B2 的值大于等于 60,“Exit Sub”语句才会执行。在本例中不符合条件,那么没有退出程序,可以继续执行其后的代码。而“Exit Sub”语句单独占居一行时,不管单元格 B2 是否符合条件,“Exit Sub”都会执行,从而退出程序,不再对 B3 的值进行判断。图 6.8 数据2.使用标签改变执行流程VBA 可以在代码中设置一个或者多个标签,然后让程序在满足某条件时跳转到标签处,从而改变过程执行流程。标签的规则是: 可以是标点符号以外的字符组合 以冒号(:)结尾 与大小写无关 必须位于一行的最左端 配合 Goto 使用例如,建立一个名为“总表”的工作表,代码如下:Sub 新建总表()For i = 1 To Sheets.CountIF Sheets(i).Name = "总表" Then GoTo errNext iSheets.AddActiveSheet.Name = "总表"Enderr:MsgBox "已经存在总表"End Sub以上代码首先利用 For 循环逐一检查工作表的名字,如果某个工作表的名字等于“总表”则执行标签“Err”之后的代码,否则继续执行 For 循环,直到循环完成并新建一个工作表、命名为“总表”。使用标签完成当前程序间的跳转时需要注意两点:(1)标签名后面必须带有冒号。(2)在标签之前根据需要,及时退出程序。在本例中,按照设计意图,只要工作簿中存在“总表”则执行标签“Err”之后的语句,反之不执行。所以标签之前必须加入“End”或者“Exit Sub”来退出程序,否则任何情况下 Err 后的语句都会被执行。在一个过程中还可以定义多个标签。例如:Excel VBA 程序开发自学通2020-2-26第 81页 /共 508页Sub 新建总表()MsgBox ActiveWorkbook.ProtectWindowsIF ActiveWorkbook.ProtectWindows = True Then GoTo 已加密For i = 1 To Sheets.CountIF Sheets(i).Name = "总表" Then GoTo 已存在Next iSheets.AddActiveSheet.Name = "总表"End已存在:MsgBox "已经存在总表"End已加密:MsgBox "当前工作簿窗口已锁定,无法建立新表"End Sub在此过程中,首先判断当前工作表的窗口是否锁定,如果锁定则执行“已加密”标签后的语句;然后再检查是否存在“总表”,当有“总表”时执行“已存在”标签后的语句。本例中两个标标签没有顺序上的差异,谁前谁后不影响代码的结果。3.SUB 过程的嵌套调用方式过程与过程之间是可以相互调用的,从而使代码的执行流程改变。通过 VBA 代码调用 Sub 子过程主要有两种方式。 Call 语句Call 语句的功能是将一个过程的控制权转移到一个过程。它的语法为:[Call] name [argumentlist],即 Call 过程名 参数。其中 Call 是可选的,即在其它过程调用过程一可以有以下两种形式:Sub 过程一()MsgBox "你好!"End SubPrivate Sub 过程二()过程一End SubPrivate Sub 过程三()Call 过程一End Sub过程二和过程三都是合法的过程调用。 Run 方法Run 方法可以运行一个宏或者调用一个函数。该方法可用于运行用 Visual Basic或 Excel 宏语言编写的宏或者运行 DLL 或 XLL 中的函数。实例如下:Sub 过程四()Application.Run "过程一"End Sub其中“Application.Run ”也可以简写为“Run”。6.2.3 过程的递归所有过程都是可以递归的,即可以调用自己来完成任务。Excel VBA 程序开发自学通2020-2-26第 82页 /共 508页实际工作中需要调用过程自己的实例极少,通常进入递归都是编码有问题而误进入递归状态,结果耗尽系统资源。在某些情况下也可以故意调用自己来完成任务。例中:1.按条件新建工作表Sub 建立 10 个表()IF Sheets.Count >= 10 Then Exit SubSheets.Add , Sheets(Sheets.Count), 1Call 建立 10 个表End Sub以上代码中,首先利用 IF 查测当前工作簿的工作表数量,如果大于等于 10 则退出程序,否则在最后位置新建一个工作表,最后再调用自身继续执行,直接满足条件“大于等于 10”为止。因代码中人为设置了退出递归的条件,所以这类递归不会造成程序崩溃,资源耗尽。如果将代码中的“IF Sheets.Count >= 10 Then Exit Sub”删除,那么程序循环执行的结果就是电脑死机,除非中途人工中断程序执行:使用快捷键【Ctrl+Break】。2.设计时钟Sub 时间()[a1] = WorksheetFunction.Text(Now(), "hh:mm:ss")Application.OnTime Now() + TimeValue("00:00:01"), "时间"End SubSub 终止()Application.OnTime Now() + TimeValue("00:00:01"), "时间", , FalseEnd Sub以上代码实现的效果为在单元格显示当前时间,包括时、分、秒,且每秒钟更新一次。通过递归方式让程序每秒钟执行一次实现时钟的效果,同时再利用另一个过程来随时退出递归。当然也可以改用快捷键【Ctrl+Break】。6.2.4 SUB 过程实例演示为了更好的理解 Sub 过程,通过两个例来展示。1. 统计选区信息:不带参数的 Sub 过程要求:对任意选区进行单元格个数、数值个数、非空单元格个数、空白单元格个数及选区之和统计。代码如下:Sub 选区统计()Dim msg As Stringmsg = "单元格个数:" & Selection.Count & Chr(10)msg = msg & "数字个数:" & WorksheetFunction.Count(Selection) & Chr(10)msg = msg & "非空单元格:" & WorksheetFunction.CountA(Selection) & Chr(10)msg = msg & " 空 白 单 元 格 个 数 : " & WorksheetFunction.CountBlank(Selection) &Chr(10)msg = msg & "选区之和:" & WorksheetFunction.Sum(Selection)MsgBox msg, 64, "选区统计"Excel VBA 程序开发自学通2020-2-26第 83页 /共 508页End Sub假设工作表中存在图 6.9 所示数据,选择 A1:D9 区域后利用快捷键【Alt+F8】执行“选区统计”过程,其统计结果见图 6.10 所示。图 6.9图 6.10工作表数据选区统计结果2.将单元格数据转换为首字母大写:带有参数的 Sub 过程要求:在工作表中选择任意一个带的英文的单元格时,将其转换为每个单词首字母大写。(1)插入模块 1,并录入以下代码:Sub 转换(Target)Selection(1) = StrConv(Target, vbProperCase)End Sub(2)双击工程资源管理器中的“Sheet1”,进入工作表代码窗口后录入代码Private Sub Worksheet_SelectionChange(ByVal Target As Range)Call 转换(Target(1))End Sub(1)返回工作表“Sheet1”,单击任意单元格,如果存在英文单词,则每个单词首字母大写,否则保持为变。如单元格格中有句子“You are on it”,那么单击该单元格后将被转换为“You Are On It”。6.3认识 Function 过程Function 过程即自定义函数,在插件中应用极广。本节介绍关于 Function 过程的语法及调用方法。6.3.1Function 过程的特点Function 过程的功能较 Sub 过程的应用范围稍小,Function 过程仅仅用于返回一个值或者多个数的组合即数组,而 Sub 过程可以返回值,还可以对引用的对象进行修改,例如引用单元格 A1 的值后对单元格 A1 设置新的格式,或者修改工作表名称等等。Function 可以获取工作表名称,但无法修改工作表的名称。Function 过程可以不使用参数,类似于工作表函数 Rand 和 Now 等等,但绝大部分函数是需要一个参数或者多个参数的,最多时可达 255 个参数。Excel VBA 程序开发自学通6.3.22020-2-26第 84页 /共 508页Function 的语法解析Function 的语法如下:[Public | Private | Friend] [Static] Function name [(arglist)] [As type][statements][name = expression][Exit Function][statements][name = expression]……End FunctionFunction 的各参数详解如下:表 6-2参数部分PublicPrivateFriendStaticnamearglisttypestatementsexpressionFunction语句参数详解功能解释可选的。表示所有模块的所有其它过程都可访问这个 Function 过程。如果是在包含 Option Private 的模块中使用,则这个过程在该工程外是不可使用的可选的。表示只有包含其声明的模块的其它过程可以访问该 Function 过程可选的。只能在类模块中使用。表示该 Function 过程在整个工程中都是可见的,但对于对象实例的控制者是不可见的可选的。表示在调用之间将保留 Function 过程的局部变量值。Static 属性对在该 Function 外声明的变量不会产生影响,即使过程中也使用了这些变量必需的。Function 的名称;遵循标准的变量命名约定可选的。代表在调用时要传递给 Function 过程的参数变量列表。多个变量应用逗号隔开。可选的。Function 过程的返回值的数据类型,可以是 Byte、 Boolean 、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(除定长)、Object、Variant或任何用户定义类型可选的。在 Function 过程中执行的任何语句组可选的。Function 的返回值和 Sub 过程一样,Function 过程也有模块级过程和工程级过程之分。Function 前置“Public”即为过程级,前置“Private”则为模块级。Function 名称在声明时需要遵循与 Sub 过程一样的规则。如果自定义的 Function 名称与 VBA 内部名称一致,仍然可以正常执行,只是以代码中调用中名的内部函数时必须声明其对象库。例如:Function sqr(AA)sqr = AA ^ (1 / 3)End FunctionSub test()MsgBox "VBA.SQR:" & VBA.sqr(27) & Chr(10) & "SQR:" & sqr(27)End Sub在以上代码中,执行 test 过程时的结果如图 6.11 所示:Excel VBA 程序开发自学通2020-2-26图 6.11第 85页 /共 508页自定义 SQR 和内置 SQR 的分别从结果可以得知,在代码使用“VBA.SQR”可以调用 VBA 自带的 SQR 功能,而直接使用 SQR 则调用自定义的 SQR 函数的功能。虽然定义函数时允许与内部函数一致,但却不允许与定义的变量或者常量一致,不管这个变量或者常量是本过程私有的还有模块中公有的,否则将产生“发现二义性的名称”的编译错误。6.3.3调用 Function 过程Function 过程通常三种方式调用:(1)在工作表中通过公式调用:像内部函数一样在工作表中使用,也可以与其它函数嵌套。(2)在 VBA 代码中被其它过程调用:就像图 6.9 对应的那段代码一样在 Sub 过程调用函数。(3)递归:Function 过程和 Sub 一样可以实现递归。如果不是刻意地、有计划地进入递归状态,可以会造成资源耗尽或者溢出堆栈空间。例如下例函数的调用:Function 递归(参数)递归 = 递归(参数)End FunctionSub 测试()MsgBox 递归(1000)End Sub将代码录入到模块中后,执行过程“测试”,立即弹错误提示“溢出堆栈空间”。为了避免递归造成的错误,甚至程序崩溃,尽量不要调用自身,开发函数、插件时多方面查核是否可能造成循环引用、递归现象。当然,有目的、有条件的递归也可以给工作带来的便利的。另外,谈到函数就不能不说它的“刷新”性能,即在工作表中使用函数时,当在其它区域的数据更新时,当前单元格的函数是否重新运算,专业术语称之为“易失性”。用户定义的函数是否有易失性可以使用以下语句来控制:Application.Volatile该语句的作是无论何时在工作表的任意单元格中进行计算时,让函数都必须重新进行计算。即工作表刷新时调用函数再运算一次,从而实现数据更新,让公式结果同步。6.4 关于过程的参数Sub 过程和 Function 过程都可以使用参数。有参数的过程相对无参数的过程更具Excel VBA 程序开发自学通2020-2-26第 86页 /共 508页灵活性,相当于给了用户更多自定义的空间。6.4.1SUB 过程的参数及应用Sub 过程的语法是:[Private | Public | Friend] [Static] Sub name [(arglist)][statements][Exit Sub][statements]……End Sub其中“(arglist)”即表示它支持可选的参数,可以不用参数,也可以使用参数;可以使用以一个参数,也可以使用多个参数。其中参数(arglist)的具体语法如下:[Optional] [ByVal | ByRef] [ParamArray] varname[( )] [As type] [= defaultvalue]表 6-3部分OptionalByValByRefParamArrayvarnametypedefaultvalueSub过程参数详解功能详解可选的。表示参数不是必需的关键字。如果使用了该选项,则 arglist 中的后续参数都必须是可选的,而且必须都使用 Optional 关键字声明。如果使用了 ParamArray,则任何参数都不能使用 Optional可选的。表示该参数按值传递可选的。表示该参数按地址传递。ByRef 是 Visual Basic 的缺省选项可选的。只用于 arglist 的最后一个参数,指明最后这个参数是一个 Variant元素的 Optional 数组。使用 ParamArray 关键字可以提供任意数目的参数。ParamArray 关键字不能与 ByVal,ByRef,或 Optional 一起使用必需的。代表参数的变量的名称;遵循标准的变量命名约定可选的。传递给该过程的参数的数据类型,如果没有选择参数 Optional,则可以指定用户定义类型,或对象类型可选的。任何常数或常数表达式。只对 Optional 参数合法。如果类型为Object,则显式的缺省值只能是 Nothing从表中可以看出,如果需要给 Sub 过程设置一个可选参数,则可使用关键字Optional 来声明,如果需要设置多个可选参数,则可使用关键字 ParamArray 来声明参数。下例即使用一个参数的 Sub 过程:Sub 过程一(msg As String)IF Len(msg) 0 Then MsgBox msg, 64, "友情提示"End SubPrivate Sub 过程二()Call 过程一("你好")End Sub如果执行过程二,将弹出图 6.12 所示对话框。Excel VBA 程序开发自学通2020-2-26第 87页 /共 508页图 6.12 提示信息可能看到以上代码时有读者会存在疑问?直接在过程二中执行 Msgbox 不是更简吗?例如改成以下代码:Private Sub 过程二()MsgBox "你好", 64, "友情提示"""End Sub在本例中确实二合一后更简单,但当有很多过程需要执行类似操作时,则在一个过程时进行判断比每个过程都判断一次会简单。例如:Sub 姓名(name As String)Dim i As Byte, rng As RangeFor i = 1 To Sheets.CountIF ThisWorkbook.Sheets(i).name = "许可人员列表" Then: GoTo OKNext iMsgBox "不存在“许可人员列表”", 64Exit SubOK:IF Len(name) 4 Then MsgBox "长度只能 2 到 4,请重新录入", 64:Exit SubSet rng = ThisWorkbook.Sheets("许可人员列表").Range("a1:a10").Find(name)IF rng Is Nothing Then MsgBox "你无操作权限" Else MsgBox "你具有操作权限"End SubSub 确认权限一() '手工指定姓名Call 姓名(Application.InputBox("请输入您的姓名", "确认权限", "", , , , , 2))End SubSub 确认权限二() '以当前表 A1 的值进行判断Call 姓名(ActiveSheet.Range("A1"))End SubSub 确认权限三() '以 OFFICE 安装用户名进行判断Call 姓名(Application.UserName)End Sub以上代码用于判断指定的用户名是否具有操作权限。在工作簿中有一个工作表名为“许可人员列表”,该表中 A1:A10 存放以 10 个允许操作的人员名单。程序会将用户输入或者指定方式获取的姓名与 A1:A10 中允许的姓名进行比较,如果与任何一个一致则提示“你具操作权限”,否则提示“你无操作权限”。在过程“确认权限一”、“确认权限二”和“确认权限三”中都可以调用过程“姓名”,只是参数不同。如果不使用过程“姓名(Name)”作过渡的话,那么过程“姓名(Name)”中的所有代码需要在后面三个过程中出现三次,每一个过程都需要对参数进行多次判断及循环,从而整个工程的代码偏长。本例文件参见光盘:..\ 第六章\确认权限.xlsm下例再演示具有两个参数但第二个参数是可选参数的 Sub 过程:Sub 改名(Sht_Name As String, Optional i As Byte = 1)Dim j As ByteExcel VBA 程序开发自学通2020-2-26第 88页 /共 508页For j = 1 To Sheets.CountIF Sheets(j).name = Sht_Name Then MsgBox "已存在:" & Sht_Name, 64: EndNext jIF i >= 1 And i = 60 Then MsgBox "及格" Else MsgBox "不及格"End SubSub Test()成绩 (59)End Sub执行“Test”过程可以正确判断成绩 59 分是否及格。但若改用 Function 过程则一定出错:Function 成绩(成绩)IF 成绩 >= 60 Then 成绩 = "及格" Else 成绩 = "不及格"End FunctionSub Test()MsgBox 成绩(59)End Sub执行过程“Test”后将弹出“当前范围内的声明重复”的编译错误。即使再修改Excel VBA 程序开发自学通2020-2-26第 89页 /共 508页为以下方式仍然报错:Function 成绩(成绩)IF 成绩 >= 60 Then MsgBox "及格" Else MsgBox "不及格"End FunctionSub Test()Call 成绩 (59)End Sub正确的方式是:Function 成绩(分数)IF 分数 >= 60 Then 成绩 = "及格" Else 成绩 = "不及格"End FunctionSub Test()MsgBox 成绩(59)End Sub6.5 开发自定义函数6.4 节对自定义函数的基础知识做了详解,本节则进行实例演示,通过带有不同参数的函数定义过程来增进读者的理解与编写功底。对于本节中开发函数中所涉及的各种语法,如条件语句、循环语句、With 语句以及常见对象的命令请参阅本书第十章和第十一章。也可以阅读完第十章及十一章后再跟着本节练习函数的开发。6.5.1开发不带参数的 Function 过程1.不随时间变化的时间函数〖要求〗:函数需要获取当前系统时间,但却不能随其它单元格的值改变更改变时间值。〖代码〗:Function Nows()'声明函数Dim Tim As String'声明一个变量Tim = Format(Now, "yyyy-mm-dd hh:mm:ss")Nows = Tim'将文本日期赋与函数End Function'获取当前时间,并转换成文本〖测试〗:将以上代码录入在模块中,然后返回工作表界面。在工作表中,A 列用于存放仓库的进库数量,而 B 列用于登记进库时间。现需求的是只要 A 列录入数据,B 列则自动产生当前时间,而且这个时间不会因为其它数据的修改而变化。在 B2 单元格录入公式:=IF(A2="","",Nows())将公式向下填充到 A100,然后返回 A2 单元格录入进数量 500,B2 则自动出现录入进库数量的时间。过半小时再在 A3 录入第二次进库的数量 800,B3 单元格则自动产生第二次进库的时间,且第一次进库时间保持不变……具体效果见 6.13 所示:〖提示〗:代码中的 Now 是 VBA 函数,不是工作表函数 NOW(),所以不需要Excel VBA 程序开发自学通2020-2-26第 90页 /共 508页带括号,但它们功能相同。〖点评〗:相对于系统自带的工作表函数 NOW,Nows 函数具有不随时间变化的优点,对于记录进库时间这类工作的应用极广。其外,如果仅仅需要不变的日期,忽略时间,可以在不改代码,而直接在公式中套用 Text 函数即可,例如:=TEXT(Nows(),"yyyy-m-d")本例文件参见光盘:..\ 第六章\自定义函数.xlsm2.取当前工作簿名〖要求〗:利用函数获取当前工作表名称,不管工作簿是否保存。〖代码〗:Function 工作簿名()'获取当前工作簿名称工作簿名 = ActiveWorkbook.Name'ActiveWorkbook 即表示当前工作簿End Function〖测试〗:进入可工作表,在单元格录入以下公式即可获取当前工作簿名,见图 6.14 所示:=工作簿名()图 6.13 测试不随时间变化的时间函数图 6.14 取工作簿名〖提示〗:未保存的工作簿也具有 Name 属性,若需要取得路径名,则需要使用FullName 属性,不过它需要在保存工作簿后才能取得。〖点评〗:Excel 没有获取工作簿名称的函数,利用 Cell 函数勉强可以完成,不过它有两个缺点:工作簿名包含路径及工作簿未保存则无法获取名称。本自定义函数不管工作簿是保存都可以顺利地获取工作簿名,不过针对未保存的工作簿就没有后缀名。6.5.2开发带有一个参数的 Function 过程1.将人民币金额转换为大写〖要求〗:对于财务报表,金额合度默认为阿拉伯数字,现需将基转换成人民币大写型式。〖代码〗:Function 大写(CELL As String) As String '声明函数名,有一个参数Dim RMBS As StringIF CELL = "" Or Not IsNumeric(CELL) Then 大写 = "": Exit Function '如果参数为空或者非数值则返回空白IF CELL = 0 Then 大写 = "零元整": Exit Function '如果参数为 0 则返回“零元整”'将数值转换成中文大写,并将点替换成“元”,将负号替换成“负”RMBS = Replace(Replace(Application.Text(Round(CELL, 2), "[DBnum2]"), ".", " 元 "),"-", "负")Excel VBA 程序开发自学通2020-2-26第 91页 /共 508页'加入角与分,同时将最后的“零”替换成“元整”RMBS = IIF(Left(Right(RMBS, 3), 1) = " 元 ", Left(RMBS, Len(RMBS) - 1) & " 角 " &Right(RMBS, 1) & "分", IIF(Left(Right(RMBS, 2), 1) = "元", RMBS & "角", IIF(RMBS = "零","", RMBS & "元整")))'将“零元”和“零角”替换成空RMBS = Replace(Replace(RMBS, "零元", ""), "零角", "")大写 = RMBS '将变量的值赋与函数End Function〖提示〗:(1)IsNumeric 用于判断参数是否是数字,非数字是无法转换成人民币大写的;(2)Replace 是用于替换的函数,但它和工作表函数 Replace 是大大不同的,倒与 SUBSTITUTE 函数极其相近。〖测试〗:如图 6.15 所示工作表中的员工工资需要汇总后以大写金额显示,那么在单元格B7 录入以下公式:=大写(SUM(B2:B6))图 6.15 汇总并转换成大写〖点评〗:Excel 自带的 Text 函数可以实现数字转中文大写,但无法实现人民币大写。借用本函数可以大幅提升财务人员的工作效率。大写函数与 Text 函数在大写方面的差异见下图:图 6.16Text 与大写函数之差异2.建立工作表目录〖要求〗:建工作表中建立当前工作簿的目录,且单击工作表名可以跳转到该工作表中。〖代码〗:Function 工作表(Optional 序号) As String '声明函数,有一个参数可选参数Application.Volatile '声明为易失性函数'如果未输入参数,则赋与变量序号为当前表的地址IF IsMissing(序号) Then 序号 = ActiveSheet.IndexIF 序号 > Sheets.Count Then '如果参数大于工作表数量工作表 = "" '返回空Else'否则工作表 = Sheets(序号).Name '取表名Excel VBA 程序开发自学通2020-2-26第 92页 /共 508页End IFEnd Function〖提示〗:(1)IsMissing 用于判断函数的可选参数是否已经传递给过程。在本例中如果不指定函数的参数,则默认返回当前工作表的表名;(2)Index 属性则是指工作表在所有工作表中的序号(从左向右数)。〖测试〗:在工作表的 A2 录入以下公式,并向下填充即可完成工作表目录地创建。当鼠标单击任意单元格 A3 时,将打开名为“工作簿名”的工作表。=HYPERLINK("#"&工作表(ROW(A2))&"!A1",工作表(ROW(A1)))图 6.17 创建工作表目录〖点评〗:Excel 本身没有获取工作表的函数,虽然调用宏表函数并借助名称可以完成,但公式较长,且必须要借助名称,公式无法在单元格中直接套用。本自定义函数以“Row(a1)”做为参数可以逐一提取工作表名,再配合“HYPERLINK”即可建立链接。3.关机函数〖要求〗:利用函数在指定时间内关闭计算机。〖代码〗:Function 关机(Optional Close_Time As Byte = 10)'声明函数名称,有一个可选参数关机 = Close_Time'在单元格显示时间Shell "shutdown -s -t " & Close_Time'在指定的时间内关闭工作表,调用的 DOS 命令End Function〖提示〗:(1)关机函数的参数使用了 Byte 数据类型,所以这个时间只能是 0 到 255 秒之间。如果需要更长的时间,可以改用 Integer;(2)Shutdown 是一个 DOS 下的程序,可以用 Shell 函数来执行。〖测试〗:在工作表任意单元格录入以下公式,那么 10 秒钟后可以关闭计算机。如果将参数设定为 3 则可以 3 秒钟后关闭。=关机()〖点评〗:Excel 本身是不具备系统控件能力的,但 DOS 下很多工具具有系统权限,而 VBA 的 Shell 函数恰好可以调用 DOS 下所有程序,所以 VBA 也就获得了对操作系统的部分控制功能。如果需要重启电脑,可以将 Shutdown 的参数改为“-S”改为“-R”。注意:测试此函数会关掉计算机,请在保存所有资料后再行测试。Excel VBA 程序开发自学通6.5.32020-2-26第 93页 /共 508页开发带有两个参数的 Function 过程1.对带“/”的数据进行合计〖要求〗:盘点产品时,部分产品以“双”为单位,部分产品无法配双则以“左/右”形式出现,现需对这种数据进行汇总,且按需求有时会按“只”计算,有时按“双”计算,公式必须具备通用性及可选性。〖代码〗:Function hesum(rng As Range, Optional 单双 As Byte = 1) '声明函数,有两个参数,第二个是可选参数Application.Volatile '声明为易失性函数Dim cell As Range, Sum1, Sum2For Each cell In rngIF InStr(cell, "/") > 0 Then '如果有“/”左 = CLng(Left(cell, InStr(cell, "/") - 1)) '提取/左边的数据右 = CLng(Replace(cell, 左 & "/", "")) '提取/右边的数据Sum1 = Sum1 + (左 + 右) '将左右相加ElseSum2 = Sum2 + cell * 2 '没有“/”则直接乘以 2End IFNexthesum = (Sum2 + Sum1) / 单双 '汇总后除以第二参数End Function〖提示〗:(1)InStr 函数用于在盘点表中查找“/”,如果查找结果大于 0,则分别取“/”左、右的数值相加,否则直接取数值本身。(2)函数的第二参数为可选函数,如果忽略参数则按“只”为单位计算,即当做 1 处理。测试:如图 6.18 所示工作表中,在 B10 单元格录入以下公式可以对盘点数进行汇总,合计数以“只”为单位:=hesum(B3:B9)图 6.18 汇总表盘表数据如果需在汇总结果以双为单位,则需要将公式改为:=hesum(B3:B9,2)〖点评〗:针对带有“/”的盘点数据,Excel 本身不存在可以直接计算的公式,Excel VBA 程序开发自学通2020-2-26第 94页 /共 508页如果用多函数嵌套也可以计算出正确结果,但公式极长,而 Hesum 函数却简短易懂。内置公式附后:=SUM(IF(ISNUMBER(FIND("/",B3:B9)),(LEFT(B3:B9,FIND("/",B3:B9)-1)+RIGHT(B3:B9,LEN(B3:B9)-FIND("/",B3:B9)))/2,B3:B9))*22.中国式排名〖要求〗:对学生成绩按班级排名,及按性别排名,且需中式排名。〖代码〗:Function 排名(区域, 成绩) '声明函数,有两个参数Application.Volatile '声明为易失性函数Dim Dic As Object, rng, i As Integer '声明变量,包括一个字典对象Set Dic = CreateObject("scripting.dictionary") '声明字典对象变量For Each rng In 区域 '遍历区域'如果变量 rng 等于成绩则为变量 i 赋值 1,如果变量 rng 大于成绩则将 rng 的值追加到字典中IF rng = 成绩 Then i = 1 Else IF rng > 成绩 Then Dic(rng * 1) = 1Next'如果变量 i 大于 0,即区域中有数据等于成绩,那么排名结果等于字典中的数量加 1(字典对象是忽略重复值的)IF i > 0 Then 排名 = Dic.Count + 1 Else 排名 = "超出范围" '如果成绩与区域中任何不等则返回“超出范围”End Function〖提示〗:(1)CreateObject("scripting.dictionary")用于创建一个字典对象,它的特点是成员不重复。而中式排名,是需要忽略重复值的。即四人中第一人 100 分算第一名,两个99 分并列第二名,而 98 分者按第三名计算,而非美式排名中的第四名。所以设计排名函数时需要借助字典这个特性来实现中式排名。(2)函数的两个参数都支持手动录入参数,而非仅仅限于单元格引用。〖测试〗:工作表中有图 6.19 所示数据,在 E2 单元录入以下公式:=排名(IF(B$2:B$10=B2,D$2:D$10),D2)再在 F2 单元格录入以下公式:=排名(IF(C$2:C$10=C2,D$2:D$10),D2)选择 E2:F2 单元格,双击单元格的填充柄将公式填充到最末尾即可完成排名计算。图 6.19 按条件排名〖点评〗:Excel 有一个内置函数 Rank 用于对成绩排名,但它是美式排名法。而Excel VBA 程序开发自学通2020-2-26第 95页 /共 508页更重的是它无法实现按条件排序,它的第一参数必须是单元格,这限制了它的功能发挥,例如以下公式 Rank 是无法运算的:=rank(3,{1,2,3,4,5})而本自定义函数是可使用内存数组作为参数的:=排名({1,2,3,4,5},3)6.5.4开发带有两个可选参数的 Function 过程1.获取可控制大小写的英文列标〖要求〗:返回指定单元格的英文列标,且可以控制列标的大小写状态。如果不指定大小写则默认为大写,如果不指定单元格,则默认计算公式所在单元格的列标。〖代码〗:Function col(Optional rng As Range, Optional style As String = "A") '声明函数名称,有两个可选参数Application.Volatile'声明为易失性函数'如果第二参数录入 A 和 a 以外的任意字符则返回空白IF style "A" And style "a" Then col = "": Exit FunctionIF rng Is Nothing Then Set rng = Application.ThisCell'如果忽略第一参数则默认取公式所在单元格'函数结果等于 Cells(1, rng)的地址去除后 1 之后所对应的字母。然后根据第二参数进行大小写控制col = StrConv(Replace(Cells(1, rng.Column).Address(0, 0), 1, ""), IIF(style = "A",vbUpperCase, vbLowerCase))End Function〖提示〗:(1)函数中非对象变量被忽略时,可以用 IsMissing 来判断,但本例中第一参数是单元格对象,所以只对用 Nothing 来判断,且在赋值时必须用 Set 语句。(2)Address 属性的两个参数使用 0 时可以将地址转换成相对引用,这有利于获取列标。〖测试〗:在工作表中录入以下公式测试 Col 函数:=Col(D2,"A")——结果为 D,第二参数大写则结果也大写=Col(D2) ——结果仍为 D,忽各第二参数则默认按大写处理=Col(D2,"a")——结果为 d,第二参数小写则结果也小写=Col(,)——如果在 C10 输入公式则结果为 C,两个参数都忽略时获取当前单元格的大写列标=Col()——如果在 F2 输入公式则结果为 F,两个参数都忽略时获取公式所在单元格的大写列标如果需要产生升序的大写字母序列,可以采用以下公式并向右填充:=col(A1)〖点评〗:Excel 自带的 Column 函数可以获取指定单元格的数字列标,无法获取英文列标,本函数与 Column 可以做互补。Excel VBA 程序开发自学通2020-2-26第 96页 /共 508页2.计算多样式星期〖要求〗:对指定日期计算星期,有四种格式可选,包括“一”、“星期一”、“Mon”和“Monday”四种。如果未指定日期则以今天为基准,如果未指定格式则以“星期一”这种中文长写为基准。〖代码〗:Function 星期(Optional dates As Date, Optional style As Byte = 2) '声明函数名称,具有两个可选参数IF dates = 0 Then dates = Date '如果忽略第一参数,则以当日计算'如果仅仅一个参数,则第参数在 1 到 4 之间,则将参数值赋与第二参数,而将当前日期赋与第一参数IF dates 1 Then style = dates: dates = DateSelect Case style '根据第二参数值选择星期的格式Case 1 '第二参数为 1星期 = WorksheetFunction.Text(dates, "aaa") '短写中文Case 2星期 = WorksheetFunction.Text(dates, "aaaa") '长写中文Case 3星期 = WorksheetFunction.Text(dates, "ddd") '短写英文Case 4星期 = WorksheetFunction.Text(dates, "dddd") '长写英文End SelectEnd Function〖提示〗:(1)第一参数声明为日期类型,那么当忽略第一参数时,不能用 IsMissing 来判断,只能判断它是否等于 0。而且当日期参数声明为可选参数时不能像第二参数一样直接赋与一个默认值:Date 或者 Now,因为声明变量时只能用常数。为了解决这个问题,只能在代码中间根据其特征判断用户在录入公式时是否已经忽略该参数;(2)本函数实例实现了自动判断所忽略的是哪一个参数的功能,即当忽略两个可选参数中的一个时,函数会判断用户忽略的是哪一个。如果唯一的参数值在 1 到 4之间,则将其赋与第二参数,将当前日期赋与第一参数。否则将唯一的参数当做第一参数计算,而第二参数以默认值 2 参与计算。〖测试〗:在工作表中录入以下公式测试星期函数:=星期()——假设今天是 2009-4-28,则结果为“星期二”,中文长写格式=星期(,3) ————假设今天是 2009-4-28,则结果为“Tue”=星期("2000-2-29",1)——结果等于“二”=星期(A1,4) ——如果 A1 为“1998-12-20”,则结果等于“Sunday”=星期(4) ——假设今天是 2009-4-28,则结果为“Tuesday”〖点评〗:Excel 自带函数 TEXT 可以实现四种星期格式的运算,但其参数对于新手来说不方记忆。开发自定义函数时需要突破这种弊障,尽量用最简单的参数表示出来;另一个值得学习的是本函数所有参数全是可选的,为用户提供最大的便利。Excel VBA 程序开发自学通6.5.52020-2-26第 97页 /共 508页开发带有不确定参数的 Function 过程1.串连内存数组及选区〖要求〗:按要求将内存数组中每个远素串连成一个字符串,同时对选定区域也进行串连。〖代码〗:Function Connect(ParamArray Rng() As Variant) '声明函数名称,有多个可选参数,包括 1到 255 个Dim cell As Range, celll As Range, i As Integer, cellv As Variant '声明变量Connect = "" '将函数初始化'遍历参数所代码的对象集合(可能是字符串,可能是区域,也可能是数组)For i = 0 To UBound(Rng)IF Not IsMissing(Rng(i)) Then '如果有参数Select Case TypeName(Rng(i)) '根据参数的类型决定计算方式Case "Range"'如果是单元格'如果参数设置过大,仅仅对参数与已用区域的重叠部分进行计算Set celll = Application.Intersect(Rng(i), ActiveSheet.UsedRange)For Each cell In celll '遍历单元格区域Connect = Connect & cell '串连所有单元格字符Next cellCase "Variant()" '如果是数组(包括内存数组)For Each cellv In Rng(i) '遍历数组'跳过 False,将数组中其它元素串连IF cellv False Then Connect = Connect & cellvNext cellvCase Else '否则Connect = Connect & Rng(i) '直接连接(指直接在参数中输入的字符串)End SelectEnd IFNext iEnd Function〖提示〗:不确定参数的函数必须使用 ParamArray 进行声明参数,使用 ParamArray时需要遵循两个规则:(1)ParamArray 所声明的参数必须位于最后位置,即除 ParamArray 声明的参数外还有其它参数时,该参数必须位于 ParamArray 声明的参数的左方。(2)ParamArray 所声明的参数必须用 Variant 数据类型。(3)Intersect 的作用于让函数只计算数据区域与参数所代码区域的重叠区,防止整列、整行或者整个工作表做参为参数造成死机。但它同时也带来了一个缺点:参数只能引用本工作表的区域,引用其它工作表或者工作簿的区域时,将会忽略。本函数在 Excel 2007 中具有 0 到 255 个参数,而在 Excel 2003 中则有 0 到 30 个参数。每个函数都是可选的。〖测试〗:工作表中有图 6.20 所示数据,为了将工号大于 2000 的员工的姓名串连成一个字符串,在单元格 D2 中录入以下数组公式:= Connect(IF(B2:B10*1>2000,A2:A10))公式必须用【Ctrl+Shift+Enter】三键组合录入才能得到正确结果。Excel VBA 程序开发自学通2020-2-26第 98页 /共 508页图 6.20 串连内存数组如果需要串连 A 列所有姓名,那么可以使用以下公式:=Connect(A2:A10)如果需要对常量数组进行连接,也可以使用以下公式:=Connect({"A","DD","S"},{1,7,100})〖点评〗:Excel 自带两个连接文件的函数:CONCATENATE 和&。然而它们共同的缺点都是不能对区域进行批量操作,也不能对数组进行串连,这使两个函数在工作中受到大大的限制。而自定义函数可以突破这两个限制,完成更复杂的工作,也是本函数的亮点。2.统计多区域公式个数〖要求〗:对多个区域计算含有公式的单元格的个数。〖代码〗:Function Functions(ParamArray rng() As Variant) '声明函数名称,有多个可选参数,包括1 到 255 个Dim cell, Fun_count As Long, i As Byte, celll As Range '声明变量IF UBound(rng) = -1 Then Functions = 0: Exit Function '如果无参数则结果为 0For i = 0 To UBound(rng)'遍历每个参数IF Not IsMissing(rng(i)) Then'如果有参数Set celll = Application.Intersect(rng(i), ActiveSheet.UsedRange)For Each cell In celll'遍历区域中每个元素IF cell.HasFormula Then Fun_tion = Fun_tion + 1 '如果有公式则累加变量Next cellEnd IFNext iFunctions = Fun_tion '统计结果End Function〖提示〗:(1)在本函数的参数中,Rng 是变体型,当做数组处理。那么进入 For 循环时默认下标为 0,不能保使用 For I = 1 to UBound(Rng)。如果参数声明为 Range 对象,那么其下标才是 1.(2)同前一个函数一样,只能对当前表区域统计公式个数。〖测试〗:在图 6.21 所示工作表中,为了统计 C 列和 F 列具有多个公式,可以使用以下公式进行计算:=Functions(C:C,F:F)Excel VBA 程序开发自学通2020-2-26第 99页 /共 508页公式可以使用 1 到 255 个参数,可以还可以累加区域。但是参数引用的区域不可以包含公式所在单元格。图 6.21 统计区域中的公式个数〖点评〗:工作表函数可以统计空单元格个数、数字个数、文本个数、大于小于某值的个数……本公式用于计算区域中的公式个数,算是对函数功能的补充。6.5.6开发具有三个参数其中第三个为可选的Function 过程1.按单元格背景颜色进行条件平均〖要求〗:按条件对与条件区域同等大小的统计区域计算平均,如果不指定统计区域则以条件区域进行计算。〖代码〗:Function AverageIFcol(条件区 As Range, 颜色单元格 As Range, Optional 统计区) '声明函数名称,有三个参数,第三个是可选参数Dim i As Integer, Counts As Integer, rng As Range, sum As Double '声明变量Application.Volatile'声明为易失性函数IF IsMissing(统计区) Then Set rng = 条件区 '如果第三参数被忽略,则将条区赋与 rng变量'如果未被忽略,那么以统计区第一个单元格为基准,向下扩充到条件区同等大于的区域赋与变量 RngIF Not IsMissing(统计区) Then Set rng = 统计区(1).Resize(条件区.Rows.Count, 条件区.Columns.Count)For i = 1 To 条件区.Count '遍历条件区'如果条件区中某个单元格背景色与颜色单元格区域(参照区)颜色一致,那么IF 条件区(i).Interior.Color = 颜色单元格(1).Interior.Color Thensum = sum + rng(i).Value '累加符合条件的数据Counts = Counts + 1 '统计符合条件的个数End IFNext iAverageIFcol = sum / Counts '最后结果等于总和除以个数End Function〖提示〗:(1)Rng 是一个中间变量,用它来替代实际统计区。当有第三参数时则等于第三参数,但参照条件区的大小;当忽略第三参数时则等于第一参数。(2)为了体现通用性,计算单元格的背景色时必须使用 Color,而不能用Excel VBA 程序开发自学通2020-2-26第 100页 /共 508页ColorIndex,否则在 Excel 2003 中可以使用,在 2007 却无法正常使用。〖测试〗:对于图 6.22 中的数据,对背景是黄色的学生的成绩计算平均。可用以下公式:=AVERAGEifcol(K9:K17,K10,L9)如果条件区和实际统计区是一个区域,可以忽略第三参数,见图 6.23 所示:图 6.22按颜色条件对统计区求平均图 6.23按颜色条件对条件区求平均〖点评〗:Excel 本身有条件求和函数——SUMIF,但无法与单元格颜色做为参照。本函数可以做为 SUMIF 函数的补充,它与 SUMIF 函数的用法一致。2.按颜色从左向向右查找所有数据〖要求〗:根据参照颜色对查找区域最左列查找同颜色的单元格,然后返回其右边若干列的数据。如果找到多个符合条件的数据,需要全部罗列出来。〖代码〗:'声明函数名称,有三个参数,第三个是可选参数,函数的结果是数组Function VlookupCol(查找值 As Range, 查找区域 As Range, Optional 列数 As Byte = 2)As VariantDim Col As Long, cell As Range, arr(), i As Byte '声明变量Application.Volatile'声明为易失性函数Col = 查找值.Interior.Color '获取参照单元格的背景色'遍历查找区域的最左边一列For Each cell In 查找区域(1).Resize(查找区域.Rows.Count, 1)IF cell.Interior.Color = Col Then '如果与参照颜色一致i = i + 1 '累加变量ReDim Preserve arr(1 To i) '重新声明数据大小,且保持数组原数据arr(i) = cell.Offset(0, 列数 - 1) '将找到的单元格右边对应的数值赋与数组End IFNext cellVlookupCol = WorksheetFunction.Transpose(arr) '将数组的结果赋与函数End Function〖提示〗:(1)Resize 属性用于调整指定区域的大小。在本例中因需要取得查找区域的最左边一列,所以需要借助 Resize 来重置区域,将行限定为原区域行数,将列限定为 1。(2)因每找到一个目标就需要重置数组 Arr 的大小,且重置时需要保留原数组的值,所以要循环中必须加入“ReDim Preserve”来声明数组。(3)Arr 数组是横向数组,本例中利用工作表函数 Transpose 将它转置为纵向数组,再赋与函数。Excel VBA 程序开发自学通2020-2-26第 101页 /共 508页(4)函数的结果是数组,如果以普通公式录入可以取得第一个查到的目标;以区域数组形式录入也可以返回所有查到的结果,假设存在多个符合条件的目标值的话。〖测试〗:在图 6.24 所示工作表中,A 列的姓名以不同背景颜色进行区分,在 E1 单元格有需要查找的参照颜色,在 E2 单元格录入以下普通公式可以返回第一个查到的目标数值 41:=VlookupCol(E1,A2:B11,2)如果需在将符合条件的所有数据全部罗列出来,则需要使用区域数组公式。选择E2:E11 区域并录入以下数组公式:=VlookupCol(E1,A2:B11,2)必须按【Ctrl+Shift+Enter】三键结束可以才得到正确结果,见图 6.25 所示。图 6.24 按颜色获取第一个目标值图 6.25 按颜色获取所有目标值因为无法确定有多少个符合条件的值,那么使用区域数组公式时无法把握好区域大小,即既要将所有结果显示出来,又不能出现错误值“#N/A”,那么可以套用 Index来完成。=IFERROR(INDEX(VlookupCol(E$1,A$2:B$11,2),ROW(A1)),"")在 F1 录入公式后,将公式向下填充即可。图 6.26 利用普通公式返回所有结果并排除错值〖点评〗:本函数可以实现按颜色进行查找,是 Vlookup 函数的补充。另外对于会用 vlookup 函数的读者一定有一个心得:vlookup 只能返回一个符合条件的目标值。使用其它函数与 vlookup 嵌套后可以实现查找所有目樯值,但公式很长,也不利于大众理解。所以在自己开发函数时,应尽量完善,且多一些可选项,让终端用户感觉实用且灵活。Excel VBA 程序开发自学通2020-2-26第 102页 /共 508页6.6 编写函数帮助用户定义的函数不管自己使用还是给其它用户使用,都有必要对函数的功能和参数添加一个说明,使用户在使用上更方便。不仅如此,还有必要对函数进行分类,例如大写函数应该划入财务函数类,那么通过插入函数的向导可以从财务函数下拉列表中找到该函数。如果打开插入函数向导,可以发现所有自定义函数默认存在于“用户定义”类别中,见图 6.27 所示。图 6.27 用户定义类别中的自定义函数列表而双击进入“函数参数”对话框后可以发现,函数的功能没有相应的说明,每个参数的说明全是空白。见图 6.28 所示。图 6.28 函数参数对话框为了解决这两个问题,需要简单的定义函数的帮助信息。VBA 中用于指定函数说明的是 Application.MacroOptions 方法。Application.MacroOptions 方法的基本语法是:Application.MacroOptions(Macro,Description,HasMenu,MenuText,Excel VBA 程序开发自学通2020-2-26第 103页 /共 508页HasShortcutKey, ShortcutKey, Category, StatusBar, HelpContextID, HelpFile)各参数的函数见表 6-4 所示:表 6-4 MacroOptions参数说明MacroDescriptionHasMenuMenuText必选/可选可选可选可选可选HasShortcutKey可选ShortcutKey可选Category可选StatusBarHelpContextIDHelpFile可选可选可选名称描述用户定义函数 (UDF) 的名称函数的描述忽略该参数忽略该参数如果为 True,则为宏指定一个快捷键;如果该参数为 False,则不为宏指定快捷键如果参数为True,则该参数为必选参数;否则忽略该参数快捷键一个指定现有的宏函数类别的整数,以确定映射为内置类别的整数,还可指定自定义类别的字符串。如果提供了一个字符串,它将作为类别名称显示在“插入函数”对话框中,如果此类别名称从未使用过,则将用该名称定义一个新的类别,如果使用的类别名称与某个内置名称相同,则 Excel 会将用户定义的函数映射为此内置类别宏的状态栏文本一个指定分配给宏的帮助主题上下文 ID 的整数包含 HelpContextId 定义的帮助主题的帮助文件名其中 Category 表示函数的类型。用户可以将自定义函数添加到属于自己的独有类型中,例如:Application.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:="andy 专用"以上代码用于将“大写”函数加入到名为“andy 专用”的新类别中,见图 6.29所示。图 6.29 将函数加入新类别中当然也可以使用内置的函数类别,例如“财务”、“文本”、“逻辑”等等。在代码中,可以直接使用类别名,也可以使用其常数值。值与类别的对应关系见表 6-5 所示:Excel VBA 程序开发自学通2020-2-26第 104页 /共 508页表 6-5 内置函数类别值12值89类别逻辑信息10命令45类别财务日期与时间数学与三角函数统计查找与引用11126数据库137文本14自定义宏控件DDE/外部用户定义3根据上表的分析,如查需要将自定义函数加入“文本”类,可以用以下语句:Application.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:=7Application.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:="文本"以上两句代码的效果一致,都可以将“大写”函数加入到内置的函数类别“文本”中。MacroOptions 方法仅仅对自定义函数生效,内置数函的任何说明性文字和分类都无法修改。使用 MacroOptions 方法添加函数的帮助分为两类:普通工作簿和加载宏。基于普通工作簿与加载工作簿的特性不同,在设置函数说明时需要区别对待。1.普通工作簿当普通工作簿文件中有自定义函数时,例如“大写”函数,利用以下一个自启动的程序加添加函数说明,即每次加载工作簿时执行。(1)如果当前工程中有“大写”自定义函数,那么在 VBE 界面是单击【插入】\【模块】;(2)在模块中录入以下自启动程序代码,该过程可以在工作簿每次开启时全自动执行:Sub auto_open()Application.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写:仅需要一个参数,单元格引用。" + Chr(10) + "例如:“=大写(a1)”", Category:="财务"End Sub(3)保存代码后退出 Excel,再重新打开簿;(4)打开“插入函数”向导,在“或选择类型”下拉列表中选择“财务”,“大写”函数即位于该类别中,见图 6.30 所示:Excel VBA 程序开发自学通2020-2-26第 105页 /共 508页图 6.30 财务函数中的“大写”函数(5)双击“大写”函数打开“函数参数”对话框 ,在对话框中有关于“大写”函数的功能、参数说明及用法,见图 6.31 所示:图 6.31 自定义函数的参数说明以后不管任何时间打开该工作簿,都可以在插入函数的向导中看到关于“大写”函数的信息,方便用户使用。2.加载宏工作簿前一种方法如果用在加载宏(xla 格式或者 xlam 格式)文件中,在开启该加载宏时将会产生以下错误提示:Excel VBA 程序开发自学通2020-2-26第 106页 /共 508页图 6.32 加载宏中添加函数说明时的错误提示这是由于 VBA 的规则决定的:在加载宏文件(加载宏的工作簿处于隐藏状态)中,不可以利用代码编辑宏。而控制文件是否具有加载宏属性的方法是改变其 IsAddin属性。所以方法一的代码需要修改为:Sub auto_open() '每次开启工作簿时执行With Application.ScreenUpdating = False '关闭屏幕更新,防止闪屏ThisWorkbook.IsAddin = False '显示加载宏工作簿'添加函数说明.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写:仅需要一个参数,单元格引用。" + Chr(10) + "例如:“=大写(a1)”", Category:="财务"ThisWorkbook.IsAddin = True '还原为加载宏End WithEnd Sub修改后的代码中,利用“IsAddin = False”使加载宏工作簿由隐藏状态还原为显示状态,然后再借用 MacroOptions 方法设置函数的说明信息,最后恢复其隐藏状态。然而利用 IsAddin 控制其状态切换将带来两个负面作用:(1)在将工作簿显示状态进行切换时,将会闪屏一次,给用户不舒服的感觉。所以代码中的“ScreenUpdating = False”则用于解决这个问题;(2)因 MacroOptions 方法修改了工作簿中宏的属性,在退出 Excel 程序时,每次都会弹出一个图 6.33 所示对话框。程序会询问用户否保存加载宏,这是一个令人极其无法接受的事。图 6.33 退出 Excel 程序时弹出的对话框为了杜绝这个问题,必须在工作簿的关闭事件中加入以下代码:Private Sub Workbook_BeforeClose(Cancel As Boolean)ThisWorkbook.Close (False)End Sub代码的含义是每次关闭当前工作簿时都不保存,从而屏弊提示框。虽然改为不用工作簿关闭事件,而直接在“auto_open”过程加入一句保存工作簿的代码也可以解决这个问题,但它将浪费一些不必要的时间。提示:本例文件参见光盘:..\ 第六章\为函数添加说明.xlam除以上两种办法外,还有一种更简单的方式——从立即窗口执行 MacroOptions方法。使用立即窗口是基于一个前提条件:EXCEL 会记录并保存 MacroOptions 的操作。也可就说只要第一次利用 MacroOptions 方法设计好后,函数中的说明就永远存在,从而避免每次启动都调用代码。具体的步骤如下:(1)打开带有函数的工作簿,使用快捷键【Alt+F11】进入 VBE 界面;(2)双击 Thisworkbook 打开工作簿事件代码窗口,使用快捷键【Ctrl+G】显示Excel VBA 程序开发自学通2020-2-26第 107页 /共 508页立即窗口;(3)在立即窗口中输入以下代码:application.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写:仅需要一个参数,单元格引用。" + Chr(10) + "例如:"=大写(a1)"", Category:="财务"(4)在该行代码最右边单击回车键,表示执行语句;(5)保存工作簿并删除立即窗口中的代码即可。重新后在插入函数向导中可以看到该语句产生的函数说明,且永久生效。如果工作簿中有多个自定义函数,那么可以录入多句代码并复制到立即窗口中,然后分别执行。“分别执行”——记住这一点很重要,立即窗口一次只能执行一行代码,所以复制三行代码到立即窗口后,需要将光标定位于每句的末尾单击一次回车……3.如何让函数说明通用于 Excel 2003 和 2007在编写函数的说明时,有必须了解 Excel 2003 和 Excel 2007 中的一些差异,从而编写代码时尽量做到代码通用。对于函数的处理 Excel 2003 和 2007 存在两方面的差异:(1)说明文字的长度不同。在 Excel2003 和 2007 中,利用 MacroOptions 方法添加的说明字符串长度是不相同的。Excel 2003 的函数参数对话框仅仅能容下 150 个以内的说明信息。2007 虽然可以达到 200 个字符以上,但为了兼容性,尽量在 150 个字之内将函数的功能、参数、用法完全说明白,否则用户在 Excel 2003 中使用时会看不到部分字符;(2)最大参数不同。当函数使用了 ParamArray 声明不确定参数时,其参数个数的上限是不相同的。在 Excel 2003 是可以最多可以使用 30 个参数,而在 Excel 2007中最多可以使用 255 个参数。所以为了让函数的说明能够适用于 Excel 2003 和 2007需要使用一些技巧。解决办法是:利用 Version 属性判断 Excel 程序的版本,如果是不是 2007 则使用30,否则使用 255。代码如下:Sub 加入函数提示()Dim counts As ByteIF Application.Version * 1 <= 11 Then counts = 30 Else counts = 255ThisWorkbook.IsAddin = FalseApplication.MacroOptions Macro:="我的自定义函数", Description:="本函数具有" & counts& "个参数,其中......", Category:="财务"ThisWorkbook.IsAddin = TrueEnd Sub6.7 总结Function 过程即自定义函数,根据工作需要可以开发自己专用的函数。对于函数的运算速度,内部集成的工作表函数一定快于用户自己定义的函数,在内部函数能够完成的情况下尽量使用工作表函数;如果工作表函数无法完成,或者需要非常长的数组公式才能完成的情况,可以开发专用的自定义函数。开发自定义函数时,应该尽量给用户一些可选项,使函数运用更简单,公式也更简短。例如内部函数 Left,取左边第一个字符时可以忽略第二参数。Excel VBA 程序开发自学通2020-2-26第 108页 /共 508页对于某些运算结果,如果工作中常见格式较多,应尽可能将所有格式全部罗列出来,让用户选择。例如本章定义的星期函数,支持四种显示方式。对于有多个结果的函数,应将函数声明为数组,将所有结果显示给用户。但为了体现灵活性,同时需要指定一种默认显示的值。例如自定义函数 VlookupCol,相对内部函数 Vlookup 在某些方面具有独特的优越性。为了让用户定义的函数在任何工作簿中都可以应用,应该工作簿保存为加载宏文件,并对其加载或者置于自启动文件夹。对于加载宏文件的生成方法参见本书第 30章。最后,为了让用户更快掌握开发者定义函数的功能,尽对函数编写说明,且对其归类。Excel VBA 程序开发自学通第七章2020-2-26第 109页 /共 508页VBA 的对象模型与对象表示法VBA 语言的绝大部分过程其实就是处理各种对象的过程。只有深入理解 ExcelVBA 涉及的不同对象及其层次结构,才能轻松驾驭 VBA 语言对对象的控制。本章主要了解 VBA 中对象的结构及其表示方法。本章要点: VBA 中的对象及结构 对象的表示法 单元格的各种引用方式7.1 VBA 中的对象及结构很多语言都集成了 VBA 环境,可以利用 VBA 来控制它们。而各种程序的 VBA语言在语法上是相通的,仅仅需要处理的对象不同。如果熟悉 VBA 基本语法,再学其它程序的 VBA,那么只需要去了解它具有什么对象就行了。这就是举一反三,也是 VBA 这类面向对象的语言的特点。同时也显露出对象在 VBA 的重要性。7.1.1关于对象的相关概念在 Excel VBA 中,工作簿、工作表、单元格、名称、批注、条件格式、图形、图表、透视表、区域等等都是对象。对象代表了应用程序中的每个元素。在 VBE 中可以利用对象浏览器来了解 VBA 支持哪些对象。单击快捷键【F2】即可弹出对象浏览器,在“工程/库”下拉列表中选择“Excel“,在左边的“类”列表中则列出了 Excel VBA 所支持的所有对象。而右边则是所选择对象的成员,包括其方法与属性。Excel VBA 程序开发自学通2020-2-26第 110页 /共 508页图 7.1 使用对象浏览器查看 Excel 对象对象是一个实体的物件,它具有一些特征,也具有读取和改变这些特征的行为,而且在改变其部分特征时,会自动产生某些预定的反应。在 VBA 中,我们将这些特征称之为对象的属性,改变特征的行为即为方法,而改变属性时引发的反应即为对象的事件。例如:Sub 工作表命名()Sheets("Sheet3").ActivateActiveSheet.Name = "工作表 3"End Sub在以上代码中,Sheets("Sheet3")代表一个名为“Sheet3”的工作表对象,Activate是一个方法,它可以激法工作表对象,相当于用户左键单击工作表名的操作。此方法改变了应用程序对象 Application 的 ActiveSheet 属性。而第二句代码的 Name 则是工作表的名称属性,该属性可以读写,即可以获取该属性值也可以改变该属性。最后,工作表的 Activate 方法引发了两个事件:当前工作表的 Deactivate 事件及 Sheet3 工作表的 activate 事件。7.1.2对象与对象集合对象是一个应用程序中可操作的元素,对象集合则为一组具有相同性质的对象的集合。例如工作表“Sheet1”是一个对象,而当所有工作表加起来就是工作表集合。其中工作表通常用 Worksheet 表示,而工作表集合用 Worksheets 表示,表示对象的复数形式。再如名称与名称集合:Name——Names条件格式与条件格式集合ormatCondition——FormatConditions单元格与单元格集合Cells(行,列) ——Cells对象与对象集合的另一个显著区别是对象没有 Count 属性,而对象集合是多个对Excel VBA 程序开发自学通2020-2-26第 111页 /共 508页象的组合体,则具有 Count 属性。如果需要引用集合中的某个成员,可以通过其在集合中的位置来引用,该位置是从 1 到集合中子集总数之间的整数。例如选择工作表集合中第三个工作表,该表名称为“表 3”,那么有以下两种方式:Sheets (3).selectSheets("表 3") select如果需要同时选择当前工作表所有的 3 个工作表,可以采用以下两种方式:Sheets(Array("Sheet1", "Sheet2", "Sheet3")).SelectSheets.Select前一种方法是将所有工作表名组成一个数组,将数组做为 Sheets 的参数即可代表一个集合,后者则直接引用 Sheets 集合。如果有三个工作簿窗口“Book1”、“Book2”和“Book3”,那么激活第二窗口也可以使用以下两种方式:Windows("Book2").SelectWindows(2). Select从以上叙述可以正确理解对象与对象集合了,但也有一些特殊的对象需要更多地体会才能厘清头绪。例如工作表对象,它既属于 Sheets 集合的成员,也属于 Worksheets 集合的成员。因为 Sheets 集合包括了所有工作表(Worksheest)对象成员和所有图表(Chart)对象成员,所以以下代码两种方式都可以生成一个新的工作表:Sheets.Add , , , xlWorksheetWorksheets.Add , , , xlWorksheet而生成一个图表则用以下代码:Sheets.Add , , , xlChart如果不指定表的类型,那么默认是创建工作表对象。另个还有一类对象——活动对象,表示当前可操作的对象。当对象有多个时,活动对象总是只有一个,它与其它非活动对象在 VBA 代码中的表达方式不同,且在外观上也有明显的不同。例如:活动工作簿:ActiveWorkbook其它工作簿:Workbooks("Book1")、Workbooks(3)等等在外观上,活动工作簿的标题颜色更深,而非活动工作簿的标题则偏灰色,处于不可操作状态。如果需要在非活动工作簿中录入数据,则需要单击该窗口,使该工作簿成为活动工作簿。Excel VBA 程序开发自学通2020-2-26图 7.2第 112页 /共 508页区别活动工作簿窗口同时,从上图中也可以看出活动单元格与非活动单元格的差异。其中 A1 是活动对象,其边框与其它单元格边框明显不同,且在行号与列标上都加深颜色以突出显示。提示:手工操作时对象时,必须先激活该对象;在 VBA 中则不需要,可以向一个非活动对象发送指令,甚至包括该对象处于隐藏状态。7.1.3对象的层次:父对象与子对象对象是有层次的,上一层称父对象,下一层为子对象。除了最上层的应用程序对象 Excel 外,任何对象都有一个或者多个父对象。父对象可以用 Parent 来表示。其语法为:expression.Parent其中 expression 表示对象。即单元格 A1 的父对象可以表示为“Range("a1").Parent”。如果需要取得工作表名称,那么可以使用以下代码:MsgBox Range("a1").Parent.Name一个更具有实用性的应用是:罗列出当前工作表中具有批注的单元格地址,具体代码为:Sub 罗列所有批注单元格()Dim nm As Comment, msg As String '声明变量For Each nm In ActiveSheet.Comments '遍历所有批注msg = msg & Chr(10) & nm.Parent.Address(0, 0) '逐一取出批注的父对象的地址NextMsgBox "以下单元格有批注:" & msg '报告结果End Sub假设工作表中有图 7.3 所示数据,其中有三个单元格有批注,执行以上代码后可以罗列出所有批注的父对象——单元格的地址,见图 7.4 所示。如果本例改用 Parent以外的任何方法都会效率更差。图 7.3工作表中的三个批注图 7.4有批注的单元格地址本例文件参见光盘:..\ 第七章\获取批注所在单元格地址.xlsmParent 也可以重复使用,表示父对象的父对象。例如单元格的对象是工作表,工作表的父对象是工作簿,那么计算当前工作簿的名称可以用以下试获取:MsgBox ActiveCell.Parent.Parent.NameParent 是对象的属性,但同时也代表一个对象——父对象。Excel VBA 程序开发自学通7.1.42020-2-26第 113页 /共 508页认识 Excel 所有对象Excel 中包含 248 种对象,由于本书的篇幅限制,不一一罗列出来,读者可以从帮助中查看。方法是:【F1】打开 VBA 帮助,单击“Excel 2007 开发人员参考”,再单击“Excel 对象模型参考”即可。根据工作性质的不同,每个程序员在编写代码时所涉及的对象也是不同的。然而常用的对象基本一致,见下表所示:表 7-1 常用对象对象名称WorkbookWindowworksheetShapeRangeNameChartCommandBarPopupCommandBar解释工作簿对象窗口对象工作表对象图形对象单元表格对象名称对象图表对象下拉菜单对象工具栏按钮对象例如 VBA 程序中常见的一句代码可能就会涉及以上的多个对象:Sub 添加矩形框()IF Application.Workbooks("book1").Sheets(6).Range("a1") = "矩形 1" ThenApplication.Workbooks("book1").Sheets(6).Shapes.AddShape(msoShapeRectangle,27.75, 4.5, 93#, 41.25).SelectEnd IFEnd Sub在以上代码中,包括了 Excel 程序对象、工作簿对象、工作表对象、单元格对象和图形对象。除此之外,菜单对象、工具栏按钮对象等等都是极常用的对象。7.2 对象的表示法VBA 对于对象的表示法有很多很多方式,最多的可以有四种方法表示同一个对象。了解对象的表示方法及如何从多种表达方式中寻找最有效率的方式是程序员必要掌握的知识范畴。7.2.1对象的完整指定方式与简写在 VBA 中对同一个对象可以有多种表达方式,但如果需要不受环境影响、总是指向正确的目标对象,则需要使用完整的引用。这类似于公式中的相对引用与绝对引用,绝对引用在任何情况下都指向目标单元格,而相对引用则完全受制于鼠标的活动。所谓完整的引有对象,即在引用对象时将其父目对象以及父对象的父象(如果存在的话)同时录入,对目标对象进行限定,从而实现对象对象的“绝对引用”。例如当前工作簿“人事资料.xlsm”中有三个工作表,需要引用其中的“总表”下Excel VBA 程序开发自学通2020-2-26第 114页 /共 508页A10 单元格,那么可以使用以下代码:Application.Workbooks("人事资料表.xlsm").Sheets("总表").Range ("A10")不管在开启多少工作簿,有多个工作表,以上代码总是指向正确的目标单元格,可以确保程序不出错。7.2.2利用定义名称获取对象每个对象都拥有自己的 VBA 名称供用户调用,对于部分对象还可以对其重新定义一个新名称,并通过用户定义的名称来获取该对象。以下常见的对象都支持定义名称:图片、剪贴、形状(在 Excel 2003 中称自选图形)、艺术字、文本框、单元格(区域).1. 图形对象的默认名称Excel 对图形对象默认的命名方式是“图形类形 编号”,例如插入第一张图片,那么其默认名称为“图片 1”,插入第三张图片则命名为“图片 3”……见图 7.5 所示。如果插入 3 个表单控制的按钮,则按钮的名称也分别命名为“按钮 1”、“按钮 2”、“按钮 3”,见图 7.6 所示:图 7.5图片对象的默认名称图 7.6按钮的默认名称在 VBA 中是可以利用这个默认名称来引用对象的。例如将“按钮 2”的宽度调整为“按钮 1”的宽度的 2 倍,可以使用以下代码:Sheets(1).Shapes("按钮 2").Width = Sheets(1).Shapes("按钮 1").Width * 2或者删除“图片 1”:Sheets(1).Shapes("图片 1").Delete2.重定义图形对象的名称所有图形对象都可以手工定义其名称。方法是选择图形对象后在名称框中录入新的名称,然后单击回车键。重新定义名称的好处是可以将图形名称变得更有意义,也便于识别。例如将图 7.5 中的“图片 1”命名为“WIN XP”,那么可以选择该图片后在名称框中录入新名称“WIN XP”再回车。重命名后的效果见图 7.7 所示:Excel VBA 程序开发自学通2020-2-26图 7.7第 115页 /共 508页为图形对象定义新名称此时可以使用新名称来引用它。例如获取名为“WIN地址,可以使用以下代码:Sub 获取覆盖区域()MsgBox Range(Sheets(1).Shapes("WININ XP").TopLeftCell).Address(0, 0)End SubXP”的图片所覆盖的区域XP").BottomRightCell, Sheets(1).Shapes("W当然,也可以使用代码对图形命名,直接修改其 Name 属性即可。例如:Sheets(1).Shapes("图片 1").Name = "AA"注意:在引用或者命名图片时,特别需要注意空格的半角与全角状态。Excel 中默认对图形对象的命名中间是半角空格,如果使用全角空格是无法引用该对象的。7.2.3利用集合索引号获取对象除最顶层的 Excel 应用程序外,所有对象都处于一个对象集合中,那么就可以利用集合中的索引号来调用对象。索用号用于对象集合,例如图形对象集合中的第二个子对象可以使用以下方式调用:Shapes(2)工作表集合中的第 10 个工作表可以用以下代码调用:Sheets(10)一个具有实用性的案例如下:Sub 统一宽度()Dim i As ByteFor i = 1 To Sheets(1).Shapes.CountSheets(1).Shapes(i).Width = 100Next iEnd Sub以 上 代 码 用 于 将 工 作 表 中 所 有 图 形 对 象 统 一 宽 度 为 100 。“ For i = 1 ToSheets(1).Shapes.Count”表示从第一个开始到最后一个图形对象逐一设置宽度。或许读者可能思维跳跃,想寻找一个更简便的方式来完成。例如:Sheets(1).Shapes.Width = 200然而事实证明这种方式无法成功,必须借助 DrawingObjects 来实现。例如Sheets(1).DrawingObjects.Width = 200另一个具有实用性的案例如下:Excel VBA 程序开发自学通2020-2-26第 116页 /共 508页Sub 反向勾选复选框()Dim i As ByteFor i = 1 To Sheets(1).CheckBoxes.CountIF Sheets(1).CheckBoxes(i) = 1 ThenSheets(1).CheckBoxes(i) = 0ElseSheets(1).CheckBoxes(i).Value = 1End IFNext iEnd Sub以上代码将工作表中所有复选框的状态进行反选,即被选中的复选框取消选择,而未选择的则勾选。执行前后效果见图 7.8 和 7.9 所示。图 7.87.2.4产品质量表图 7.9反选复选框活动对象的简化引用前面的对象引用方式可以确保总能正确的引用对象,然而却是牺牲效率来实现的。当所引用的对象是当前活动对象时 VBA 提供了简化的引用方式。在英文中,active 的含义是活动的,现役的,而在 VBA 它正好用于表示当前已激活的对象。例如:活动工作表——ActiveSheet活动单元格——ActiveCell活动工作簿——ActiveWorkbook活动窗口——ActiveWindow在引用活动对象时是不需要罗列其上层对象的,而是直接使用“ActiveCell”或者“ActiveWorkbook”等等。如图 7.10 中引用 B4 单元格的值,可以使用以下两种方法:ActiveCellWorkbooks("业务报表.xlsm").Sheets("生产表").Range("B4")Excel VBA 程序开发自学通2020-2-26图 7.10第 117页 /共 508页业务报表前者在写法上较后者更简洁,而且在执行效率上也有大大的优势。因为 VBA 在引用对象时按点来处理的,父对象层级越多执行效率越差。另一点需要特别指出的是,ActiveCell 是 Application 对象的成员,所以不能使用以下两种方法引用活动单元格:ActiveSheet.ActiveCellSheets(1).ActiveCell但是 Application.ActiveCell 中的 Application 却可以忽略。而且 Application 对象的很多很多成员都可以忽略 Application 而直接引用。例如:Application.ActivePrinterApplication.CalculateApplication.DisplayAlertsApplication.Intersect7.2.5利用 WITH 语句简化对象引用With 语句的功能是代码减肥,且加快快程序执行速度。不过它的前提条件是同一对象需要多次引用时。Excel 的录制宏也同一对象多次引用时,也存在自动调用 With 语句简化对象引用的智能。例如录入一个宏,对当前选区添加黄色背景色,录制宏产生的代码如下(后面的注释为笔者添加):Sub Macro1()With Selection.Interior.Pattern = xlSolid '填充图案.PatternColorIndex = xlAutomatic '图案的颜色.Color = 65535'背景色.TintAndShade = 0'颜色深浅度.PatternTintAndShade = 0 '对象的淡色和底纹图案End WithEnd Sub如果在 Excel 2003 中则产生以下代码:Sub Macro1()With Selection.InteriorExcel VBA 程序开发自学通2020-2-26第 118页 /共 508页.ColorIndex = 36.Pattern = xlSolidEnd WithEnd Sub从以上两段代码都以看出,录制宏时,如果同一对象连续多次调用时可以使用With 来简化。如果第一段代码不使用 With,将其还原后将是:Sub Macro1()Selection.Interior.Pattern = xlSolid'填充图案Selection.Interior.PatternColorIndex = xlAutomatic'图案的颜色Selection.Interior.Color = 65535'背景色Selection.Interior.TintAndShade = 0'颜色深浅度Selection.Interior.PatternTintAndShade = 0 '对象的淡色和底纹图案End Sub或许这种方式编写的代码更利于初学者理解,然而追求执行效率才是开发者们的首选。在程序开发中,只要同一对象超过两次引用,一定要借助 With 语句来简化。7.2.6事件中的 Me 关键字Me 关键字是隐含声明的变量。它通常用于事件过程中,表示当前过程的对象。关于对象的事件在第 8 章中将详细解说,此处仅需了解 Me 关键字所代表的对象即可。如果在工作表事件中使用 Me,则它代表当前工作表;如果在工作簿事件中使用Me,则它代表工作簿;如果在窗体中使用 Me,则它代表窗体对象。例如进入工作表“Sheet2”时报告工作表中有多少个 OEL 对象(ActiveX 控件),那么可以使用以下步骤完成:(1)在工作表标签中的“Sheet2”中单击右键,从弹出的菜单中选择【查看代码】;(2)在当前代码窗口中录入以下代码:Private Sub Worksheet_Activate()MsgBox "当前表 OLE 对象有:" & Me.OLEObjects.CountEnd Sub(3)返回工作表界面,单击进入工作表“Sheet1”,再次单击“Sheet2”工作表从而激活代码,VBA 程序立即弹出对话框,表示当前表中 OLE 对象的数量,见图 7.11所示。图 7.11报告“Sheet2”OLE 对象数量在上面的代码中 Me 关键字代表“Sheet2”工作表对象。本例文件参见光盘:..\ 第七章\ ME 关键字使用.xlsm或许读者会认为“Me.OLEObjects.Count”完全可以简写为“OLEObjects.Count”,Excel VBA 程序开发自学通2020-2-26第 119页 /共 508页那么关键字“Me”有何存在价值?事实上它有两个作用:作为过程函数调用和提供成员列表1.作为过程函数调用仍以图 7.11 的工作簿为例,现追加一个要求:进入工作表“Sheet1”中报告已用数据区域地址,其操作步骤如下:(1)在工作表标签中右键单击“Sheet1”,从弹出菜单中选择【查看代码】;(2)在代码窗口中输入以下代码:Private Sub Worksheet_Activate()Call 已用区域(Me)End Sub(3)单击菜单【插入】\【模块】,并录入以代码带参数的过程:Sub 已用区域(Obj As Object)MsgBox Obj.UsedRange.Address(0, 0)End Sub(4)返回工作表界面,通过单击工作表标签切换工作表。当进入“Sheet1”工作表时,将弹出一个对话框,表示当前工作表已用区域的地址,见图 7.12 所示:图 7.12报告 Sheet1 已用区域地址Me 在窗体中也可以代表窗体本身。2.提供成员列表VBA 中每个对象都有很多属性、方法等等成员,程序员不该将时间花在记忆所有成员的单词上。在 Excel 中,只要输入对象名及小圆点,那么 VBA 会自动列出所有成员,程序员仅仅从中选择即可。而关键字 Me 本身代表一个对象,那么也同样具这有个功能。例如在录入前面的代码“Me.OLEObjects.Count”时,如果不记得它的具体单词,仅仅知道前一个字母是“O”,那么可以录入“Me.”即可,VBA 会自动列出其所有成员列表,见图 7.13 所示:Excel VBA 程序开发自学通2020-2-26图 7.13第 120页 /共 508页利用 Me 获取成员列表在实际工作中,使用Me 关键时需要注意以下事件:(1)关键字 Me 不能出现在标准模块中,因为标准模块不能代表对象。若从类模块中复制代码,则必须用指定对象或窗体名来取代 Me,以保持原来的引用。通常只能在事件过程中使用,包括工作表代码窗口、工作簿代码窗口和类模块中。(2)关键字 Me 不能出现在 Set 赋值号的左边。例如:Set Me = MyObject而正确的对Me 赋值是使用 Let 或者见忽略,例如:Let Me = MyObject7.3 单元格的各种引用方式Excel 中有很多对象,最常用的对象当属单元格对象,它是数据最基本的载体。而 VBA 中对单元格的表示方法也较其它对象更多、更复杂,在本小节专门讲述单元格与区域的表示方法。单元格的最基本的表示方式有三种:Range("a1")方式、cells(1,1)方式和[a1]方式,另外还有交集、合集、偏移量、已用过域、当前区域、End 等等单元格引用的相关概念。7.3.1 Range("A1")方式引用单元格用 Range 可以将文本型的单元格地址转化为单元格对象引用,类似于工作表函数“INDIRECT”。它可以引用单元格、区域、整行、整列及整个工作表。1.引用单元格Range 引用单元格对象是方式为:单元格的列标加行号做为参数,且左右加入引号。例如:Range ("A1")——表示 A1 单元格Range ("C25")——表示 C25 单元格Range ("ZZ1048576")——表示 ZZ1048576 单元格,在 Excel 2003 中是无效的引用,因为 Excel 2003 的最大行不超过 65536 行,最大列不超过 IV 列Range ("A1")本身是代表一个单元格对象,但在“MsgBox Range("A1")”语句中Excel VBA 程序开发自学通2020-2-26第 121页 /共 508页则可以获取单元格的值。事实上“MsgBox Range("A1")”是“MsgBox Range("A1").value”的简写。每个对象都有很多属性,同时也都有一个默认属性,而单元格的默认属性是“Value”,所以如果不明确指出属性时,那么一定是调用它的 Value 属性值。Range 参数中的引号必须是半角状态下输入,否则必将产生编译错误。另一个需要重点是 VBA 中 Range("A1")方式引用对象时是不区分相对引用和绝对引用的,不管使用 Range("A1")、Range("$A1")、Range("A$1")还是 Range("$A$1")都引用同一单元格,而且在循环中也不产生任何影响。所以为了简化,通常只用 Range("A1")这种形式来引用单元格。2.引用区域Range 引用区域时是利用区域左上角单元格地址加冒号再加右下角单元格地址为其参数。不过参数也可以写成右下单元格地址加冒号再加左上角单元格地址,VBA会自动将其转换成左上角单元格地址加冒号再加右下角单元格地址的形式。例如以下两种方式引用区域都可以得到相同结果:MsgBox Range("A2:D1").AddressMsgBox Range("D1:A2").Address以下是一些合法的区域引用:Range ("A1:V10")——代表从 A1 到 V10 的矩形区域,包括 220 个单元格Range("F2:F10000")——代表从 F2 到 F10000 的矩形区域,包括 9999 个单元格Range("D2:ZZ10000")——代表从 D2 到 ZZ10000 的矩形区域,包括 6989301 个单元格,在 Excel 2003 是不合法的引用方式,因为它的最大列只有 IV 列区域的默认属性也是 Value,但是区域的 Value 是一个数组,包括多个对象,VBA中无法直接将其显示在屏幕上。如果利用 Msgbox 来显示这个属性值将得到一个运行时错误,例如图 7.14:图 7.14 引用区域默认属性时产生运行时错误正确的方式是逐个引用区域中单个值。通过索引号做参数来实现。例如:Range("D2:Z10")(1)——代表 D2:Z10 区域中第一个单元格的 Value,即 D2Range("D2:Z10")(3)——代表 D2:Z10 区域中第三个单元格的 Value,即 F2Range("D2:Z10")(24)——代表 D2:Z10 区域中第 24 个单元格的 Value,即 D3也就是说,索引号代表区域中从左到右、从上到下的序号,它是区域左上角单元Excel VBA 程序开发自学通2020-2-26第 122页 /共 508页格的参照进行相对引用。如果索引号为小数时,VBA 会自动对其进行四舍五入。如:MsgBox Range("D3:E7")(1.5).Address——结果为“$E$3”,参数 1.5 当做 2 处理MsgBox Range("D3:E7")(4.4).Address——结果为“$E$4”,参数 4.4 当做 4 处理事实上,索引号可以使用两个参数,第一参数表示行的索引,第二参数表示列的索引。那么参数“(4,5)”就可以引用区域中第 4 行第五列的单元格,它以区域左上角单元格为参照,而非工作表中 A1 单元格为参照。例如以下的引用:MsgBox Range("D3:F7")(1, 3).Address——结果为“$F$3”,表示 D3:F7 区域第一行第三列MsgBox Range("D3:F7")(4, 2).Address——结果为“$E$6”,表示 D3:F7 区域第四行第二列区域的参数还可以使用 0 和负数,甚至大于区域中单元格个数以及小于 0,同样是合法的引用。当行索引参数为 0 时,则向区域中的左上角单元格向上偏移一个单位;当列参数为 0 时,则向区域中的左上角单元格向左偏移一个单位;如果参数是负数,在继续追加偏移量。例如:MsgBox Range("D3:F7")(0, 0).Address——结果为“$C$2”,即 D3 向左及向上偏移一个单位MsgBox Range("D3:F7")(-1, -2).Address——结果为“$A$1”,即 D3 向左偏移两个单位,再向上偏移三个单位MsgBox Range("D3:F7")(9, 4).Address——结果为“$G$11”,即 D3 向下偏移九个单位,再向右偏移四个单位。虽然其行数与行数都已超过区有区域的大小,便有然可以正确的引用单元格Range 的参数也支持表达式,即字符或者数值运算结果。例如:Range("F" & 3 + 2)——表示引用 F5 单元格Range("F" & Range("D5").Value)Range("D" & WorksheetFunction.Min([a:a]) & ":G5")还可以使用变量做与参数,这在循环语句中极为有用。例如:Range("D" & i)——表示列标为 D,行号为变量 i 的值的单元格引用。3.引用多区域如果在参数是使用多个区域的地址,且用半角逗号分隔,那么 Range 也可以引用多个区域。例如以下引用方式:Range("D3,F7")——表示 D3 和 F7 两个区域,包括了 2 个单元格Range("D3:F4,G10")——表示 D3:F4 和 G10 两个区域,包括 7 个单元格Range("A1,B3:F4,Z1:ZB2")——表示 A1、B3:F4 和 Z1:ZB2 三个区域,包括 1317个单元格此方式引用单元格有一个限制:参数的长度不能超过 256 个字符,否则将会产生运行时错误。4.引用整行、整列利用“行号:行号”做为参数时可产生对整行的引用,同理利用“列标:列标”做Excel VBA 程序开发自学通2020-2-26第 123页 /共 508页为参数时可产生对整列的引用,如果两个行号或者列标不一致时,可以引用多行或者多列。以下是一些合法的引用:Range("2:2")——表示引用第二行Range("2:10")——表示引用第二到十行Range("D:d")——表示引用第 D 列,列标不区分大小写Range("D:Z")——表示引用从 D 列开始到 Z 列结束的区域Range("D:A")——表示引用 A 列到 D 列,顺序不一致时,VBA 会自动转换成升序格式参数中的冒号可以用半角也可以用全角,VBA 会将其全角冒号转成半角冒号。但是引用却只能使用半角,否则将产生编译错误。整行、整行引用对象除了 Range 法外,还可以用 Rows 和 Columns 来完成。其中ROWS 引用行,以阿拉伯数字做为参数;Columns 引用列,即可用阿拉伯数字做参数,也可用列标做参数。Rows(2)——表示引用第二行Rows("2")——同样表示引用第二行Rows("2:2")——仍然表示引用第二行Rows("2:4")——表示引用第二到四行Columns(2)——表示引用第二列,相当于 Range("B:B")Columns("B")——同样表示引用第二列Columns("B:B")——仍然表示引用第二列Columns("B:D")——表示引用 B 到 D 列如果不带参数,那 Rows 代表整个工作表所有行,包括 17179869184 个单元格。而 Columns 代表整个工作表所有列,仍然包括 17179869184 个单元格。5.Range 嵌套使用除以上的四种方法外,Range 还支持利用单元格做为参数,其具体语法为:Range(Cell1, Cell2)其中 Cell1 和 Cell2 是必选参数。Cells1 用于指定目标区域的左上角单元格,Cell2用于指定目标区域右下角单元格。如果使用一个或者三个单元格将产生编译错误。例如以下引用方式全是合法的区域引用:Range(Range("A1"), Range("D2"))——表示引用 A1:D2 区域,包含 8 个单元格Range(Range("A4"), Range("A100"))——表示引用 A1:A100 区域,包含 97 个单元格当然也有一些特殊的应用,当参数并非单个单元格,而是区域时,取两个区域所跨越的最大范围。例如:Range(Range("A1:A3"), Range("D2"))——表示引用 A1:D3 区域,而非 A1:D2。VBA会从两个区域中最左上角的单元格做为新的区域的参照起点,再取两个区域所跨越的最大行做为新的区域的行数,取两个区域跨越的最大列做为新区域的列数。Range(Range("B2:A3"), Range("A3:D10")——表示引用 A2:D10 区域。要理解这个算法,可以分别将 B2:A3 和 A3:D10 两段字符配对,然后从前两个字符中最最小值,再从后两对字符中取最大值,再加上冒号组合成一个新的区域地址。例如“B2:A3”和“A3:D10”,第一对字符 B 和 A 中取出最小值 A,然后第二对字符2 和 3 中取出最小值 2,,再从第三对字符 A 与 D 中取出最大值 D,最后从 3 和 10 中Excel VBA 程序开发自学通2020-2-26第 124页 /共 508页取最大值 10,将这四个字符与冒号串连起来即为“A2:D10”。7.3.2 Cells(1,1)方式引用单元格利用 Cells 引用单元格也有三种用法。1.WorkSheet.Cells(横座标,纵座标)引用某工作表中行、列座标所指定的单元格,可以使用本方式,基本语法为:[Sheet].Cells([RowIndex],[ColumnIndex])——其中工作表对象可选,行与列座标也可选本方式可以引用某个工作表中横座标与纵座标之交叉点,该座标原点在左上角,向右偏移一个单位即为 A 列,向下偏移一个单位即第一行,那么 Cells(1,1)即为 A1 单元格。如果代码中忽略工作表对象,则默认指当前工作表;如果忽略横座标与纵座标号,则默认引用所有单元格。以下是几种合法的单元格引用:Sheets(1).Cells(5, 4)——表示引用第一个工作表中行座标为 5、列座标为 4 的单元格 D5Sheets("生产表").Cells(10000, 1000)——表示引用“生产表”中 ALL1000。在 Excel2003 中该语句将出错,因为其最大列为 2562.WorkSheet.Cells(行号,列标)本引用方式依靠目标地址的行号与列标来确定目标单元格。其中行号与列标两个参数都是必选参数。而工作表对象 Worksheet 则是可选参数。以下三个引用为合法的单元格对象引用:Sheets("生产表").Cells(2, "C")——表示引用“生产表”中 C2 单元格Cells(12, "ZZ")——表示引用当前表 ZZ12 单元格Cells("12", "ZZ")——仍然表示 ZZ12 单元格,即行号并非一定要使用双引号,但列标一定要使用双引号本方法引用单元格永远只能引用一个单元格,不能引用区域。3.Range.Cells(横座标,纵座标)本方式引用单元格是以其父对象 Range 左上角单元格做为参照系,向下及向右累加的座标系数来指定单元格。有别于第一种在工作表中 A1 单元格为参照。例如以下单元格引用:Range("B2:G10").Cells(2, 2)代码表示 B2:G10 单元格中横座标为 2、纵座标为 2 的单元格 C3。利用图展示它们的关系则可以表现为:图中黄色单元格如果相对于工作表,那么其横纵座标分别为 3 和 3,但对于 B2:G10区域,其横纵座标则为 2 和 2。Cells 的参数还可以使用小数,不过 VBA 会将其进行四舍五入后再计算座标。例如:Range("B2:G10").Cells(1.5, 4.4)——表示引用 B2:G10 区域第二行、第四列 G3 单Excel VBA 程序开发自学通2020-2-26第 125页 /共 508页元格还可以使用负数或者 0 做为参数,那么其座标计算方式则向左与向上偏移。例如:Range("D4:G10").Cells(-1, -1)——表示引用 B2 单元格Range("D4:G10").Cells(0, -2)——表示引用 A3 单元格图 7.16 是负数座标的图示:图 7.15 图示 Cells(2, 2) 与 Range("B2:G10")的关系图 7.16 图示 Range("D4:G10").Cells(-1,-1)4.Range.cells(索引号)当使用单个索引号做为参数时,它表示父对象中的一个索引子集。其编号方式是先行后列、先左后右。例如以下的引用:Range("B2:G10").Cells(5)——表示 B2:G10 区域中第 5 个单元格 F2,从 B2 开始向右 5 个单位Range("B2:G10").Cells(7)——表示 B2:G10 区域中第 7 个单元格 B3,因父对象只有6 列,那么从第二行开始累加一个,即第 7 个单元格Range("B2:G10").Cells(60)——表示引用单格 G11。在 B2:G10 区域中仅仅 54 个单元格,而参数 60 超过区域的最大个数后,则继续向其下一行开始累加,直到超出整个工作表的边界7.3.3 [a1]方式引用单元格[a1]方式引用单元格是在左、右方括号直接录入单元格或者区域地址来引用目标的方式,它不区分大小写,也不区分相对引用还是绝对引用。[a1]方式引用单元格在写法上占在较大优势,而可以引用单元格、区域、整行、整行等等,简便且灵活。例如以下几种方式都是合法单元格引用:[a1]——表示引用单元格 A1[B$10]——表示引用单元格 B10[D2:F500]——表示引用 D2:F500 区域,包括 1497 个单元格[D2,F2]——表示引用 D2 和 F2 两个单元格[D2:D3,F2:G10,Z100]——表示引用 D2:D3 和 F2:G10、Z100 三个区域,包括 21Excel VBA 程序开发自学通2020-2-26第 126页 /共 508页个单元格[D2:D3,D5]——表示引用 D2:D3 和 D5 两个区域,中间的冒号和逗号允许使用全角,VBA 会自动将其转换为半角而以下引用则是不合法的单元格引用:["D2:D3"]——参数不能使用引号[A1:F2500000]——行数超过允许的最大值 10485767.3.4 Range("A1")、Cells(1,1)与[a1]比较在实际工作中,三种方法各有所长。表 7-2 是它们在功能上的一些差异比较:表 7-2 三种单元格引用方式比较引用方式比较项目可以引用的对象自动列出成员用于代码循环输入简便性支持参数Range("A1")Cells(1,1)[a1]单元格、区域、多区域、整行、整列支持行循环差索引号、Item和Cells单元格单元格、区域、多区域、整行、整列不支持行循环、列循环差Item和Cells不支持不支持好不支持从以上的比较中可以发现,Cells(1,1)的优势在于代码循环中可以进行行与列循环,缺点是无法引用区域;Range 的优势在于支持自动列出成员,支持行循环和参数,缺点是书写时不够方便;而[A1]方式的优势在于书写方便、可以引用区域,缺点是不支持循环和不能自动列出成员。其中需要强调的有三点:支持自动列出成员和循环。(1)自动列出成员对象是否支持自动列出成员是很重要的一个特点,在编写代码时,可能程序员对某些属性或者方法不够熟悉,需要借助自动列出成员来快速完成。那么需要记住用何种方式可以调用其成员。单元格的三种引用方式中,仅仅第一种支持自动列出成员,在代码窗口录入“[A1].”或者“Cells(1,1)”后不会有任何反应。( 2 ) 所谓 支持 参 数是 指 访问 其子 集 。访 问 区域 的子 集 有三 种方 法 。例 如Range("A1:A10")第二个子集,那么有以下三种方式:Range("A1:A10").Item(2)Range("A1:A10").Cells(2)Range("A1:A10")(2)而[A1]方式引用单元格时仅仅支持两种方式引用子集。例如:[A1:A10].Item(2)[A1:A10].Cells(2)而“[A1:A10]. (2)”则是非法引用.Cells(1, 1)”表示访问区域中第一个子集,而[](3)支持循环VBA 编程时循环语句使用非常频繁,那么使用支持循环的引用方式对于工作具Excel VBA 程序开发自学通2020-2-26第 127页 /共 508页有较大的便利性。现例举一个循环的案例:Sub 标示补考人员()Application.ScreenUpdating = False: Rem 关闭屏幕更新Dim Row_Count As Byte: Rem 如果成绩超过 255 个,则不用声明为 ByteFor Row_Count = 2 To 101: Rem 遍历所有成员Rem 如果成绩小于 60,则在右边一个单元格显示“需补考”IF Cells(Row_Count, 2) < 60 Then Cells(Row_Count, 3) = "需补考"Next: Rem 循环检查下一个Application.ScreenUpdating = True: Rem 恢复屏幕更新End Sub在以上循环中,Cells 的参数使用变量从而实现所有成绩单元格的引用,如果[A1]方式引用成绩则无法完成。鉴于 Range ("A1")方式也可进行行的循环,那么此程序也可以改为以下形式:Sub 标示补考人员 2(): Rem Range 方式Application.ScreenUpdating = False: Rem 关闭屏幕更新Dim Row_Count As Byte: Rem 如果成绩超过 255 个,则不用声明为 ByteFor Row_Count = 2 To 101: Rem 遍历所有成员Rem 如果成绩小于 60,则在右边一个单元格显示“需补考”IF Range("B" & Row_Count) 0 Then'如果有错误MsgBox "未找到带有公式的单元格", 64, "提示"'弹出提示Else'否则rng.Select'选择所有公式所在单元格End IFEnd Sub提示:SpecialCells 方法引用单元格可以通过录制“定位条件”的宏来完成,即在录制宏时可以产生关于 SpecialCells 的所有代码,用户在不熟悉参数用法的时候可以通过录制宏来获取代码。“条件定位”对话框见图 7.27 所示。图 7.27 SpecialCells 方法对应的“定位条件”对话框7.3.9 CurrentArray:引用数组区域数组公式是用于建立可生成多个结果或可对在行和列中排列的一组参数进行运算的单个公式。而数组区域是包含同一个数组公式的单元格集合。数组区域有两个显著特点:(1)公式两端有花扩号“{}”数组公式包括两种,单元格数数组公式和区域数组公式。它们都有相同的外观特征:公式两端自动产生花扩号“{}”。图 7.28 即可典型的数组公式(2)无法单独编辑其中某个单元格区域数组公式跨越多个相邻的单元格,用户无法单独编辑其中任何单元格,包括修改、删除等等。如果需要修改区域数组公式,需要选择整个数组区域。图 7.29 为编辑数组区域中单个单元格时的出错提示:Excel VBA 程序开发自学通图 7.282020-2-26典形的数组公式与数组区域第 136页 /共 508页图 7.29编辑数组区域中单个公式时出错数组区域可以利用 CurrentArray 来引用,其具体语法为:Range.CurrentArrayRange 可以是数组区域中的任意单元格。例如图 7.26 中,单元格 A1 的数组区域和 A2 的数组区域完全一致。如果 Range 不要数组区域中,则 Range.CurrentArray 引用将产生错误。下面通过两个案例讲解 CurrentArray 用法。案例一:将当前表所有数组区域的公式转为数值Sub 将数组区域转为值()On Error Resume Next '当错误值继续执行Dim rng As Range, Arrays As Range '声明变量'遍历公式区域(包括普通公式和数组公式,如果有的话)For Each rng In Range("A1:H10").SpecialCells(xlCellTypeFormulas, 23)Set Arrays = rng.CurrentArray '将数组区域赋与变量IF Err.Number = 0 Then Arrays = Arrays.Value '如果不存在错误则将数组区域转换成值Err.Clear '清除错误设置Next rngEnd Sub本例文件参见光盘:..\ 第七章\转换数组区域的公式为值.xlsm7.3.10 Resize:重置区域大小Resize 用于调整指定区域的大小,返回代表调整后的区域。它有具体语法为:Range.Resize(RowSize, ColumnSize)其中参 RowSize 代表重置后的行数,ColumnSize 代表重置后的列数。两个参数皆为可选参数,如果省略参数,则表示新区域中的行数或者列数保持不变。以下是一些合法的单元格引用:[a1].Resize(2, 2)——表示 A1:B2,包括两行两列 4 个单元格Cells(3, 2).Resize(1, 4)——表示 B3:E3,包括 1 列 4 行共 4 个单元格Range("B1:C2").Resize(4, 4)——表示 B1:E4,包括 4 行 4 列共 16 个单元格,在其前置对象“B1:C2”中仅仅取 B1 做为参照除将单元格重置为区域,将小区域重置为更大的区域外,Resize 也可以将区或转换为单元格,或者将大的区域转换为更小的区域。例如:Range("B1:C2").Resize(1)——表示 B1:C1,将原区域两行重置为 1 行,而列数保持不变Range("B1:C2").Resize(1, 1)——表示 B1,行与列都调整为 1。为了简化通常不使用这种引用方式,而是改用索引号——Range("B1:C2")(1)Excel VBA 程序开发自学通2020-2-26第 137页 /共 508页Range([a2], [c10]).Resize(4, 5)——表示 A2:E5,将原区域列数增大,行数减小[A:A].Resize(1, 16384)——表示第一行,将原有的整列转置为整行,仅仅 2007 可用Resize 的参数可以使用小数,VBA 会将其进行四舍五入进行转换成整数。但是参数不可使用负数和 0,否则将产生运行时错误。[A4:B7].Resize(2.5, 2.4)——表示 A4:B5,重置为 3 行 2 列也可以利用表达式做为 Resize 的参数,例如:[A4:B7].Resize(1, [A1] + 5)——重置为 1 行,A1 的值加上 5 列[A4:B7].Resize(WorksheetFunction.Sum([c:c]),WorksheetFunction.Min([a1:C2]))——重置后的行数与列数由 C 列之和及 a1:C2 区域最小值决定根据以上的参数解说,可以对 Resize 方法有了较深认识。下面举两个案例以拓宽读者的思路:实例一:复制 Sheet2 已用区域到 Sheet1 表的 A1Sub 复制工作表已用区域()'利用 With 简化对象引用With Sheets(2).UsedRange'将 Sheet1 的 A1 单元格重置为 Sheet2 已用区域的大小,然后再将其赋值为 Sheet2中已用区域的值Sheets(1).[a1].Resize(.Rows.Count, .Columns.Count) = .ValueEnd WithEnd Sub在对一个区域的赋与另一个区域时,需要确保两个区域的高度和宽度一致,否则将产生错误。而利用 Resize 做可以计算数据源的大小,再将目标区域重置为相同大小,那么赋值就不再有问题。实例二:隔一行插行 N 行假设在工作表前 20 行实现隔一行插入 N 行,而 N 需要由用户指定,那么以下代码完全可以实现:Sub 隔一行插入 N 行()Application.ScreenUpdating = False '关闭屏幕刷新Dim i As Integer, Row_Count As Byte '声明变量Row_Count = InputBox("隔行插入几行?", "确定行数", 1) '用户指定插行的行数For i = 20 To 1 Step -1 '从最大值循环至第一行Cells(i, 1).Resize(Row_Count * 1, 1).EntireRow.Insert Shift:=xlDown '插入行Next i[a1].Resize(Row_Count * 1, 1).EntireRow.Delete '删除第一行前插入的行Application.ScreenUpdating = True '恢复屏幕更新End Sub执行以上代码时将弹出一个对话框,让用户指定行数,默认值为 1,见图 7.30 所示;如果录入 2,并执行程序,那么数秒钟后在工作表中每行数据前都将产生两个空行,见图 7.31 所示:Excel VBA 程序开发自学通2020-2-26图 7.30 指定插入行数第 138页 /共 508页图 7.29 插入新行的状态7.3.11 Offset:根据偏移量引用区域Range 对象的 Offset 属性可以返回一个 Range 对象,代表位于指定单元格区域的一定的偏移量位置上的区域。其具体语法如下:Range.Offset(RowOffset, ColumnOffset)RowOffset 表示行偏移量,ColumnOffset 表示列偏移量。其两个参数均为可选参数,如果忽略参数量,其默认值为 0,即引用原有的区域。以下语句均为合法的单元格引用:[a1].Offset(2, 3)——表示相对于 A1 单元格向下偏移 2 行、向右偏移 3 列,即引用D3 单元格Range("D2").Offset(, 4)——表示相对于 D2,行偏移为 0 列偏移为 4,即引用 H2 单元格VBA 中的 Offset 与工作表函数 Offset 在高度与宽度上有差异。工作表函数中Offset 有五个参数,后两个参数用于指定目标区域的高度与宽度,而 VBA 中的 Offset属性则没有高度与宽度的参数,而是由其前置对象 Range 来决定。它的高度与宽度都与 Range 完全一致,例如:Range("D2:C3").Offset(1, 1)——表示相对于 D2:C3 区域向下偏移一行、向右偏移一列,且高度一宽度与 D2:C3 一致的区域 D3:E4Range("D2:D10").Offset(, 4)——表示引用 H2:H10,从原区域向右移动四列,高度与亮度一致Offset 的参数也可以使用负数。如果 RowOffset 参数使用负数则表示向上偏移,而 ColumnOffset 参数使用负数则表示向右偏移。Range("D4:D10").Offset(-2, 4) ——表示引用 H2:H8 单元格,在原有区域基础上向上偏移两行Cells(3, 4).Offset(-1, -2)——表示引用 B2 单元格,在原单元格 D3 基础上上移一行、左移两列Cells(3, 4).Offset(-1, -4)——列偏量太小,已超过 Excel 的边界,所以产生运行时错误Offset 的参数支持小数,VBA 会自动将其四舍五入后再参数与运算。例如:Cells(3, 4).Offset(0.9, 2.4)——表示引用 F4 单元格,参数 0.9 当进位为 1 计算,而2.4 舍位为 2 计算还可以使用表达式做参数,列如:Range("F2").Offset(WorksheetFunction.Sum([C:C]), [a1] + 5)——目标区域由 C 列数据和及 A1 的值决定Excel VBA 程序开发自学通2020-2-26第 139页 /共 508页Offset 在实际工作中应用极广,现举三个案例应用。实例一:仍然是交换座次,但相对于 7.3.6 中的实例要求上有一点不同:每组的行标题改为组名。在交换座次时,不能移动任何组别名称。图 7.32 座次表本例需要使用 Offset 来完成,完整代码如下:Sub 数据交换() 'Offset 应用Dim rng As Range, adds As String, i As Byte, j As Byte, rngg'获取所有姓名所在地址ForEachrngInActiveSheet.UsedRange.Resize(1,ActiveSheet.UsedRange.Columns.Count)IF Len(rng) > 0 Then adds = adds & rng.Address(0, 0) & ","Next rngWith Range(Left(adds, Len(adds) - 1))'统计组别个数j = .Areas.Count'将最后一个区域的值存入内存中,Offset 的作用是向下偏移一行,从而避免移动标题rngg = .Areas(1).CurrentRegion.Offset(1)For i = 1 To j - 1'遍历最后一个区或以外的所有区域'将下一个区域的值赋于当于区域.Areas(i).CurrentRegion.Offset(1)=.Areas(i+1).CurrentRegion.Offset(1).ValueNext i.Areas(j).CurrentRegion.Offset(1) = rngg'再将内存中的值赋于最后一个区域End WithEnd Sub本例文件参见光盘:..\ 第七章\交换座次(Offset).xlsm实例二:对任何数据选区进行列合计。例如图 7.33,如果选择数据区域 B2:E10,那么程序就对该对该区域的各行汇总,再对各列汇总,汇总结果放在 F2:F10 及 B11:F11区域。同时需要体现程序的通用性,即不管选择任何区域都可以实现对该对域的数值进行行与列的汇总。利用 Offset 可以有效地处理本类问题,具体代码如下:Sub 行列自动合计()'先汇总各行的值For i = 1 To Selection.Rows.Count '从 1 到总行数'利用 Offset 取得汇总数据的放置位置,即选区第一个单元格向右偏移选区的列数'合计区域也用 Offset 逐行偏量来获取,Resize 的作用是重置为 1 行,否则会汇总其它行的数据Selection(1).Offset(i1,Selection.Columns.Count)=WorksheetFunction.Sum(Selection.Offset(i - 1).Resize(1))NextExcel VBA 程序开发自学通2020-2-26第 140页 /共 508页'再汇总各列的值For i = 1 To Selection.Columns.Count + 1 '从 1 到总列数加 1,因为需要对行的汇总数再进行汇总Selection(1).Offset(Selection.Rows.Count,i1)=WorksheetFunction.Sum(Selection.Offset(, i - 1).Resize(, 1))NextEnd Sub首先选择数据区或 B2:E10,然后执行程序,计算结果见图 7.34 所示:图 7.33 等汇总的生产数据图 7.34 行列汇总结果该程序以选区为基准,所以使用前需要选择正确的区域。实例三:复制 sheet2 中的数据到当前表空白区当前表已使用部分区域,现要求对 Sheet2 中 2 行标题以外的数据复制到当前表空白区。利用 Offset 可以完成,代码如下:Sub 复制数据()With Sheet2.UsedRange'With 减少对象的引用次数'利用 Offset 取得当前表已用区域之后第一个空白单元格,配合 Resize 将区域重置为与Sheet2 标题以外的数据一样大小'然后将两个相同大小的区域直接赋值即可。但在赋值时需要注意一个问题:Value 不能省略ActiveSheet.UsedRange.Cells(1,1).Offset(ActiveSheet.UsedRange.Rows.Count).Resize(.Rows.Count - 2, .Columns.Count)= .Offset(2, 0).Resize(.Rows.Count - 2, .Columns.Count).ValueEnd WithActiveSheet.UsedRange.Borders.LineStyle = xlContinuous'对已用区域添加边框End SubOffset 在很多地方都与 Resize 配合使用,可以随心所欲重置区域大小与位置。如果单用 Resize,它的基点总固定在区域左上角位置,而单独使用 Offset,则不管偏到什么位置,其高度和宽度受原区域限制,而两者搭配却可以突破所有限制。本例文件参见光盘:..\ 第七章\复制标题以外的数据到当前表非空区.xlsm7.3.12 Union:单元格的合集单元格合集即将多个单元格或者区域并为一个区域。在工作中,需要用到合集的地方较多,特别在查找目标单元格时。引用区域集合可用 Application 对象的 Union 方法,其具体语法为:Application.Union(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10,Excel VBA 程序开发自学通2020-2-26第 141页 /共 508页Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20, Arg21, Arg22,Arg23, Arg24, Arg25, Arg26, Arg27, Arg28, Arg29, Arg30)其中前个两个为必选参数,后面 28 个为可选参数。其返回结果为单元格对象。例如同时引用 A2:B2 和 D3:G4 两个区域,那么可以利用 Union 方法合并两个区域,然后再引用该新区域即可。方法如下:Application.Union([A2:B2], [D3:G4])如果读者还记得前面所介绍的 Range 引用多区域的方法,那么可能会认为不使用Union 方法仍然可以引用多区域的合并区域。例如以上区域可以表示为:Range("A2:B2,D3:G4")然而,Range 参数的字符限制使它在多区域应用方面无法取代 Union,当程序长度超过 256 个字符时必将产生编译错误。而 Union 方法则可以突破这个屏障。现举两个应用案例体现 Union 在工作中的用途与优势。案例一:选择当前表中所有产量超过 100 的单元格,数据见图 7.35:图 7.35 机台产量数据首先利用循环获取所有目标单元格地址,代码如下:Sub 选择大于 100 的单元格()Dim rng As Range, msg As String'声明变量For Each rng In Range("B2:I13")'遍历整个数据区域'如果大于 100 则取出其地址,并与逗号串连IF rng.Value > 100 Then msg = msg & rng.Address(0, 0) & ","Next rng'判断下一个MsgBox Left(msg, Len(msg) - 1) '取出所有地址End Sub目标区域地址为“B2,C2,D2,E2,F2,G2,H2,I2,C3,E3,F3,G3,H3,I3,F4,G4,H4,I4,B5,C5,D5,E5,F5,G5,H5,I5,B6,C6,D6,E6,F6,G6,I6,B7,C7,D7,E7,F7,G7,H7,I7,B8,C8,D8,E8,F8,G8,H8,I8,B9,D9,E9,F9,G9,H9,I9,D10,F10,G10,H10,I10,B11,C11,D11,E11,F11,H11,I11,B12,C12,D12,E12,F12,G12,H12,I12,B13,C13,D13,E13,F13,G13,H13 ”。很显然,超过 256个字符,如果使用以下语句选择目标单元格一定生产运行时错误。Range(Left(msg, Len(msg) - 1)).Select如果改用 Union 方法则可以突破字符长度问题。代码如下:Sub 选择大于 100 的单元格 2() 'Union 法Dim rng As Range, Rang As Range'声明变量For Each rng In Range("B2:I13")'遍历整个数据区域'如果大于 100 则取出其地址,并与逗号串连Excel VBA 程序开发自学通2020-2-26第 142页 /共 508页IF rng.Value > 100 Then '如果对象变量 rng 大于 100IF Rang Is Nothing Then '如果变量 rang 还没有初始化Set Rang = rng '将符合条件的第一个目标赋与变量 RangElse'否则'合并两个对象(已查找到的区域与当前符合条件的单元格合并为一个区域)Set Rang = Union(Rang, rng)End IFEnd IFNext rng'判断下一个Rang.Select'选择合并区域,即所有符合条件的单元格End Sub本例文件参见光盘:..\ 第七章\选择大于 100 的所有单元格.xlsm案例二:隔行着色对 1 到 20 行中隔行添加背景色,代码如下:Sub 隔行着色()Dim rng As Range, rngg As Range '声明变量For Each rng In Range("A1:A20") '遍历 A1:A20IF rng.Row Mod 2 = 1 Then '如果行号是奇数IF rngg Is Nothing Then '如果 Rngg 未初台化Set rngg = rng.EntireRow '将对象变量 Rng 的整行赋与变量 RnggElse '否则Set rngg = Application.Union(rng.EntireRow, rngg) '将变量 Rng 与 Rngg 合并End IFEnd IFNext rngrngg.Interior.ColorIndex = 17 '添加背景色End Sub代码中两个重点:(1)利用 Mod 函数判断行号是否奇数(2)对奇数行合并为一个区域,最后对该区域着色。合并后权需着色一次,否则需要着色 10 次。这是 Union 函数的优势。本例文件参见光盘:..\ 第七章\隔行着色.xlsm7.3.13 Intersect:单元格、区域的交集交集是指两个或者是多个区域的重叠部分。获取多区域的交集可以使用 Application 对象的 Intersect 方法,其具体语法如下:Application.Intersect(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10,Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20, Arg21, Arg22,Arg23, Arg24, Arg25, Arg26, Arg27, Arg28, Arg29, Arg30)其中前两个参数为必选参数,其余 28 个为可选参数。相对于合集,区或的交集在工作中应用更广,常常利用它来提升代码执行效率。以下为几种常见的交集引用方式:Application.Intersect([a1:a10], [2:2])——表示引用单元格 A2,A1:A10 与第二行的交界处是 A2,通过图 7.36 可以观察什么是交集:Excel VBA 程序开发自学通2020-2-26第 143页 /共 508页图 7.36 两个区域的交集Intersect([B2:F10], Range("A2:G3"))——表示引用 B2:F3 区域,Application 可以忽略不写Intersect([B2:F10], Range("G3"))——两个区域这存在重叠区,运行时将产生错误。如果利用 TypeName 来查看,可以得到 Nothing,表示两者不相交现通过两个案例讲读对 Intersect 方法有进一步认识。案例一:在 D、E 和 G 列双击输入日期要求在当前工作簿的任何工作表中 D 列、E 列和 G 列双击就可以日期,而其它单元格则忽略。那么需要使用工作簿事件(关于工作簿事件的详解请参阅本书第八章),且配合 Intersect 与 Union 来完成。代码如下:Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target AsRange, Cancel As Boolean)'首先将 DE 列和 G 列合并为一个区域,然后将其与当前活动单元格进行判断,如果重叠就退出程序IF Intersect(Union([D:E], [g:g]), Target(1)) Is Nothing Then Exit Sub'活动单元格中输入日期Target(1) = DateEnd Sub将以上代码录入 ThisWorkbook 代码窗口中,然后返回工作表,在 D 列、E 列或者 G 列任意单元格双击即可返回当前系统日期,而不影响其它单元格。代码中 Union 的作用是将三列合并为一个区域,再与当前单元格进行比较,如果重复就允许双击录入日期,否则退出程序。本例文件参见光盘:..\ 第七章\双击输入日期.xlsm案例二:将工作表中已区域的标题填充底纹,将数据区添加边框图 7.37 是生产表数据,现需对其行标题及列标题添加灰色底纹,对数据区域添加边框。代码需要通用,即工作表中添加、删除行或者列后都可以完全适用,不需要修改任何代码。Excel VBA 程序开发自学通2020-2-26图 7.37第 144页 /共 508页待设置底纹与边框的生产表利用 Offset 和 Union 来完成,代码如下:Sub 设置边框与底纹()Dim rng As RangeSet rng = ActiveSheet.UsedRange '将已用区域赋与变量,变量的使用目录是减化代码的长度'对已用区域向下偏移一行,以及向右偏移一列,再将两个区域的重叠区域设置边框Application.Intersect(rng.Offset(1,0),rng.Offset(0,1)).Borders.LineStyle=xlContinuous'对已区域和 A 列的重叠区域添加底纹Application.Intersect(rng, [a:a]).Interior.ColorIndex = 15'对已区域和 1 行的重叠区域添加底纹Application.Intersect(rng, [1:1]).Interior.ColorIndex = 15End Sub本例中多次使用 Intersect 引用交集,从而避免引用多余的区域。因为 Offset 在进行偏移时,将新区域保持原区域大小,而实际待操作区域大小稍有差异,那么利用Intersect 来引用多区域的交集就可以排除多余的区域。当然,如果读者对 Resize 的功能比较熟,本例也可以改用 Resize 来改变区域大小,从而实现 Intersect 类似的功能。本例文件参见光盘:..\ 第七章\为生产表添加边框与底纹.xlsm案例三:统计选区中与 A1 背景色一致的单元格个数工作表的数据区域有多种颜色,现需计算与 A1 单元格颜色一致的单元格个数。代码如下:Sub 计算选区中背景色等于 A1 背景色的单元格个数()Dim rng As Range, colors As Long, Item As Longcolors = Range("A1").Interior.Color '获取 A1 单元格的颜色(一个 Long 型数值)'在选区及已用区域的交集中循环'目的是防止用户选择太多的区域时浪费程序时间For Each rng In Application.Intersect(ActiveSheet.UsedRange, Selection)'如果某单元格的颜色与 A1 颜色一致则累加计数器IF rng.Interior.Color = colors Then Item = Item + 1Next rng'报告计数器MsgBox "共有:" & Item & "个", 64, "颜色统计"End SubExcel VBA 程序开发自学通2020-2-26第 145页 /共 508页代码中使用 Color 而不用 ColorIndex 是因为 ColorIndex 仅仅能表示 0 到 56,可以适用于 Excel2003 的颜色数量,而 2007 的颜色远远超过 56 种,使用 ColorIndex 来表示颜色会产生错误,仅仅多种不同种颜色的单元格的 ColorIndex 完全相同。为了避免这个错误及兼容 Excel 2003,必须用 Color 获取单元格颜色值。本过程中 Intersect 的作用是将循环区域限制在已用区域中,以防止用户全选或者选择整列时共费不必要的时间。因为 For 循环是在用户的选区中一直进行比较,直到最后一个单元格,不管单元格是否空白。即合整工作表只有两个单元格已用,当选择整列再执行时,仍然需要循环 1048576 次,而事实上必要的循环只要两次。而 Intersect的作用正是弥补这种缺憾。在自定义函数中有时也需要使用 Intersect 来减少循环次数,避免在非使用区域中循环计算而浪费时间。本例文件参见光盘:..\ 第七章\颜色统计.xlsm7.3.14End:引用源区域的区域尾端的单元格如果活动单元格在一个较大的数据区域中间,按下快捷键【Ctrl+上箭头】、【Ctrl+下箭头】、【Ctrl+左箭头】、【Ctrl+右箭头】可以迅速将当前单元格移至已用区域的边缘;如果整个工作表均为空白则可以在最小行、最小列或者最大行、最大列之间切换;如果在空白区向数区域使用同方向的快捷键,则可以定位于该行(列)的第一个或者最后一个非空单元格。Range 对象的 End 属性正好对应于以上四个快捷键,可以实现最上端、最下端、最左端和最右端的切换。它的具体语法为:Range.End(Direction)其中必选参数 Direction 表示所要移至的方向,有四个常量,见 7-7 所示:表 7-7End四个参数详解名称值xlDown-4121xlToLeft-4159xlToRight-4161xlUp-4162描述向下向左向右向上以下语句配合图示或者可以更清晰、明了地理解 End 属性的用法与技巧。所有代码都基于图 7.38 进行演示,如果工作表中数据有变化,则代码可能无法得到正确结果。(1)引用 C 列第一个非空单元格,代码如下:Range("C1").End(xlDown)——从空白单元格 C1 向下,遇到第一个非空单元时停止Range("C4").End(xlUp)——从非空区域中间向上移,遇到最后一个非空单元格时停止Range("C1048576").End(xlUp). End(xlUp)——双 C 列最后一个非空单元格向上,遇到第一个非空单元格时停止,再次向上,遇到最后一个非空单元格时停止。Excel VBA 程序开发自学通2020-2-26第 146页 /共 508页在本例中三种方法都可以得到正确结果,然而三种方法都有缺陷:如果 C1 单元非空,即整个数据区域包括了 C1 时,那么第一种方法会出错;如果 C4 与 C 列第一个非空单元格之间有一个空单元格间隔,那么第二种方法会出错;如果 C4 与 C 列第一个非空单元格之间有两个空单元格间隔,那么第三种方法会出错。图 7.38测试数据那么如何才能获取 C 列第一个非空单元格且代码通用呢?重点在于借用 IF 函数判断 C1 是否非空。完整代码如下:Sub 获取 C 列第一个非空单元格地址()Dim msg As StringIF Len([c1]) > 1 Then '如果 C1 非空msg = "C1" '变量 Msg 赋值为 C1Else'否则msg = Range("C1").End(xlDown).Address(0, 0)格End IFMsgBox msg '报告地址End Sub'赋值为 C1 向下第一个非空单元代码思路为:利用 Len 计算 C1 单元格的字符长度,长度为 0 则是空单元格。如果非空则直接取 C1 单元格地址做为结果,否则利用 End 属性的“xlDown”参数向下移动,直到遇到第一个非空单元格,并取其地址。(2)引用第四行最后一个非空单元格,代码如下:[XFD4].End(xlToLeft)——2007 专用以上代码在光盘文件中完全可以实例需要的结果,然而它也有两个缺陷:如果第四行最后一个单元格非空,则计算会出错;如果在 Excel 2003 中执行代码也会出错,因为 Excel 2003 不存在 XFD 列。以下代码具备通用性,可以完成需求,且避免所有隐患。代码如下:Sub 获取第四行最后一个非空单元格地址()Dim msg As String'利用工作表最大列数做为 Cells 的第二个参数可以引用最末列的单元格IF Len(Cells(4, Columns.Count)) > 1 Then '如果第四行最末列非空msg = Cells(4, Columns.Count).Address(0, 0) '变量 Msg 赋值为第四行最末列的地址Else'否则msg = Cells(4, Columns.Count).End(xlToLeft).Address(0, 0) '赋值为第四行最末列向左第一个非空单元格End IFMsgBox msg '报告地址Excel VBA 程序开发自学通2020-2-26第 147页 /共 508页End Sub从以上代码中,可以了解到 End 属性获取最后一个非空单元格地址的方法,还可以学习在代码中引用最末行或者最末列时如何让代码兼容 Excel 2003 和 Excel 2007。鉴于代码的通用性问题,“Cells(Rows.Count, 1)”是 A 列最后一个单元格的最精典引用方式。另外有一个问题值得注意,如果工作表中第 4 行为空行,那么语句“Cells(4,Columns.Count).End(xlToLeft)”将引用 A4,即无法找到非空单元格时,即引用第四行第一个单元格。(3)获取 E 列已用区域下面第一个空白行的行号Cells(Rows.Count, 4).End(xlUp).Row + 1代码中的 Row 表示获取单元格的行号。本代码对于 Excel 2003 和 Excel 2007 都通用。至于另一个关于最后一行非空的问题基本上可以不用考虑,因为用户不可能将工作表中 65536 行(Excel 2003)或者 1048576 行(Excel 2007)都存放数据。本例文件参见光盘:..\ 第七章\ End 属性演示.xlsm下面再通过两个案例演示 End 属性在工作中的具体应用,来促使读者对 End 有进一步认识。案例一:将当前表以外的所有工作表都合并到当前表图 7.39 成绩表与汇总表工作簿中第一个表为“汇总表”,其它工作表分别为各班级的成绩表,班级个数不确定。现需将所有班级的数据全部复制到“汇总表”中,并按先后顺序存放。代码如下:Sub 合并三个班成绩到总表() '必须当前表是汇总表时执行Dim sht As Worksheet '声明变量For Each sht In Sheets '遍历所有工作表IF sht.Name ActiveSheet.Name Then '如果 sht 的名字不等于当前表名字'如果工作表 A 列非空(本程序要求工作表的数据必须从 A 列开始存放)IF WorksheetFunction.CountA(sht.[a:a]) > 0 Then'将工作表 sht 中 A1 到最后一个非空行之间的所有行复制到当前表的从上到下第一个空行sht.[a1].Resize(sht.Cells(Rows.Count,1).End(xlUp).Row,Columns.Count).Copy _ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Offset(Len([a1]) > 0, 0)End IFEnd IFNext sht '复制下一个End Sub本代码在合并各表的数据时,具有以下三个智能:首先,不管当前哪一个表是活动工作表,不影响成绩合并,总能将成绩数据复制到“汇总表”;Excel VBA 程序开发自学通2020-2-26第 148页 /共 508页其次,如果成绩表 A 列为空白,则忽略该表;最后,每次复制数据到汇总表中时都会自动追加到第一个空白行,使不覆盖、丢失数据的同时,所在数据刚好整齐排列,不产生空行。本例如果使用 Usedrange 获取每个成绩表的数据,然后合并可以使用代码更简短。然而 UsedRange 只能在正常情况下使用,若工作表中有假空行(删除数据却保留了格式),那么复制数据时将会把空行也复制这来。而使用 End(xlup)则完全可以避过这个问题,它以单元格是否有数据为标准进行判断,而忽略格式。所以本例采用 End(xlup)来提升代码的通用性。本例文件参见光盘:..\ 第七章\合并所有班级成绩到总表.xlsm案例二:录入数据时自动定位下一行第一个空单元格工作表各行中已录入的数据个数不同,现要求每次录入数据后自动定位于下一行第一个空单元格,以提高入速度。实现以上需求可以利用工作表事件来完成(对于事件的详解请参阅本书第八章),代码如下:'声明工作表事件Private Sub Worksheet_Change(ByVal Target As Range)'如果只在一个单元格中编辑数据就执行事件过程IF taregt.Count = 1 Then'使用 Cells 参数 Columns.Count 是为了兼容 Excel 2003,Target.Row + 1 则表示下一行With Cells(Target.Row + 1, Columns.Count).End(xlToLeft)'自动选择下一行第一个非空单元格.Offset(0, -(Len(.Text) > 0)).SelectEnd WithEnd IFEnd Sub将以上代码录入在工作表 Sheet1 的事件代码窗口中,然后返回工作表,在 D2 单元格录入数据后回车,Excel 会立即定位于第三行第一个空单元格 C3;而 C3 录入数据后则会自动定位于第四行第一行空单元格 B4……图 7.40 送货表本例文件参见光盘:..\ 第七章\自动定位下一行第一个空单元格.xlsm单元格引用的方式非常非常多,也远比其它对象更复杂,读者需要多多练习,并多多阅读他人的 VBA 代码实例,从中学习、借鉴对象引用的技巧。Excel VBA 程序开发自学通2020-2-26第 149页 /共 508页自动宏与 Excel 事件第八章Excel VBA 具有很多智能,程序自动化执行即为其中一种。利用 VBA 的事件可以使代码在不同的条件下自动执行,且该触发条件有近百种。本章针对自动宏及 Excel 事件的分类及触发条件等等将会做详细地解说,促使读者对Excel 的事件驾驭得更娴熟。本章要点: 让宏自动执行 详谈 VBA 的事件 VBA 有哪些事件8.1 让宏自动执行早在 Excel 5 中就出现了自动宏,它可以让程序自动化。而后续版本中则引进事件来进一步强化程序的自动执行能力。8.1.1Auto 自动宏在 VBA 中,只要将宏的名称命名为“Auto_Open”,且保存在模块中,那么开启工作簿时就可以自动执行该宏程序。对应的,如果宏程序的名称命名为“Auto_Close”,且代码保存在模块中,那么在关闭工作簿时也可以自动执行该宏程序。例如每次开启工作簿时让工作簿的状态栏显示“四维实业公司人事报表”,而关闭工作簿时自动保存工作簿。Sub auto_open()Application.StatusBar = "四维实业公司人事报表"End SubSub auto_close()ThisWorkbook.SaveEnd Sub以上两段代码中,第一段为工作簿打开时设置其状态栏文字为“四维实业公司人事报表”,在工作簿开启时自动执行,见图 8.1 所示;第二段则可以让工作簿关闭时自动保存,而不用用户手工单击保存。Excel VBA 程序开发自学通2020-2-26图 8.1第 150页 /共 508页修改报表状态Auto 自动宏给 Excel 用户来极大的便利。但是在后续的各个版本中,Excel 引进了更先进的事件来执行自动宏,意味着 Auto_open 宏的淘汰。本例文件参见光盘:..\ 第八章\ Auto 自动宏.xlsm8.2.2工作簿事件中的自动宏在 Excel 2007 中,可以使用工作簿级的“Workbook_Open”事件来执行自动宏。不过为了体现兼容性,微软并没有将 Auto 宏禁用,仍然可以继续使用,但绝不推荐用户继续使用。Workbook_Open 事件的用法如下:(1)事件的代码必须写在 Thisworkbook 代码窗口中(2)该事件过程的外壳如下:Private Sub Workbook_Open()End Sub(3)该事件无法利用录制宏产生,但可以将录制宏的代码复制到 Workbook_Open事件代码中。如果需要砖达成前面的 Auto_open 宏相同效果,那么将其代码导入到事件中即可:Private Sub Workbook_Open()Application.StatusBar = "四维实业公司人事报表"End Sub对应 Auto_Close 宏也有相应的工作簿关闭事件:Workbook_BeforeClose。它可以在关闭工作簿时自动执行。如果需要达成 8.1.1 节中的相同效果,那么可以使用以下代码:Private Sub Workbook_BeforeClose(Cancel As Boolean)ThisWorkbook.SaveEnd SubWorkbook_BeforeClose 事件接受一个参数,该参数用于控制工作簿是否允许用关闭,其详情将在下一节中展示。如果用户同时写入了 Auto 宏和 Workbook_Open 事件程序,它的执行顺序如何呢?先来做一测试:(1)在 Thisworkbook 代码窗口录入以下代码:Private Sub Workbook_Open()MsgBox "Workbook_Open 事件"Excel VBA 程序开发自学通2020-2-26第 151页 /共 508页End Sub(2)单击菜单【插入】\【模块】;(3)在模块中录入以下代码:Sub Auto_Open()MsgBox "Auto_Open 宏"End Sub(4)保存工作簿并重启工作簿,可以发现首先弹出“Workbook_Open 事件”,然后是“Auto_Open 宏”。本例文件参见光盘:..\ 第八章\自动宏与事件的顺序.xlsm8.2.3利用鼠标移动事件执行自动宏除了以上代全自动的执行代码外,Excel 也支持其它的很多方式来引发代码执行。例如鼠标移过图片是,报告图片的边距。具体实现步骤如下:(1)进入工作表“Sheet1”,并单击功能区【开发工具】\【插入】\【图像(ActiveX控件)】(图标为:);(2)在工作表中按下左键并拖动,从而绘出一个图像控件;(3)对图片控制单击右键,并选择菜单【属性】,从而打开“属性”窗口,见图8.2;(4)单击“Picture”属性右边的浏览图标 ,从“加载图片”对话框中选择目标图片并单击“确定”按钮返回工作表,图像控制的效果如果 8.3 所示:图 8.2图像控件的属性窗口图 8.3加载图片后的图像控件(5)单击功能区中的【设计模式】切换按钮,从而退出设计模式;(6)右键单击工作表标签中的“Sheet1”,选择菜单【查看代码】打开 VBE 界面中的“Sheet1”代码窗口;(7)在代码窗口中录入以下代码:Private Sub Image1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)MsgBox Image1.Name & ":左边距为" & Image1.Left & " 上边距为" & Image1.TopEnd Sub(8)按下快捷键【Alt+F11】返回工作表,当鼠标移过图片时,将会弹出图 8.4所示代码,Excel VBA 程序开发自学通2020-2-26图 8.4第 152页 /共 508页鼠标移过时报告其边距以上过程 Excel 的事件引发程序自动执行,在下一节中,将详细的讲述 Excel 所支持的各种事件。本例文件参见光盘:..\ 第八章\鼠动鼠标报告图片边距.xlsm8.2详谈 VBA 的事件在 VBA 中,工作簿、工作表、窗体、控件、图表等等都支持事件,通过事件可以实现办公自动化。每个 VBA 爱好者都需要深入地理解事件的分类,及每个事件在引发条件、实现功能,从而提升工作效率。8.2.1事件的定义与分类事件在 Excel 中执行某个操作时引发预定义的动作。事件过程则是一个事件发生时所引发由用户编写的 Sub 过程。事件类型无法自定义,但事件发生后引发的动作却可以人为控制。例如在工作表中有一个 Change 事件,它表示工作表任意单元格的值改变时所引有的事件,如果用户利用鼠标、键盘或者程序去试图修改工作表中任何单元格,那么就会引发名为 Change 的事件。用户无添加一个工作表本身没有的事件,如鼠标移过事件,却可以随意定制 Change 事件触的过程。Excel 支持上百种事件,如果需要进行分类通常按代码的存放位置和事件的级别来区分。表 8-1 中即 7 个类别的事件及其代码存放位置列表。表 8-1事件级别应用程序级工作簿级工作表级图表级窗体级ActiveX控事件分类代码存放位置引发对象类模块任何工作簿Thisworkbook工作表(Sheet1、Sheet2等等)图表(Chart1、Chart2等等)UserForm工作表或者窗体中当前工作簿当前工作表当前图表当前窗体当前控件Excel VBA 程序开发自学通2020-2-26第 153页 /共 508页件类模块用户定义的对象类模块其中每个级别的事件都包含多个事件。如果需要了解每个事件的含义,可以从帮助文件中获取完整解释。例如需要查看工作表事件,那么只需要查找“Worksheet 对象成员”,Excel 的帮助浏览器就会罗列出所有关于工作表事件,见图 8.5 所示。图 8.58.2.2工作表事件列表事件的层次与执行顺序事件是区分层次的,不同层次的事件在执行顺序上有先后之别。1.事件的层次Excel 的事件具有不同的层次。其中最顶层是应用程序事件,不管任何工作簿都可以引用该事件;其次是工作簿事件,只有当前工作簿才可以触发;最底层是工作表事件,只有在代码所在的工作表才可以触发;而工作表中的 ActiveX 控件和工作表事件、图表事件属于同一层次。Excel 事件的层次遵循高层次事件包含低层次事件的原则。如应用程序除了具有它自己的事件外,还包括工作簿事件,工作表事件,即某个操作可以触发工作表事件或者工作簿事件时,它同时也可以触发工应用程骗子级别的事件。而工作簿事件除了自身事件外,也包括其下属的工作表事件。一个可以触发工作表事件的操作同时也可以触发工作簿事件。例 如 工 作 表 级 别 的 “ Worksheet_Activate ” 事 件 , 同 时 会 触 发 工 作 簿 级 别 的“Workbook_SheetActivate”事件,以及应用程序级别的“Application_SheetActivate”事件。工作表事件属于最低层的事件,它只自己属于自己的事件。不同层次的事件过程只能在自己专属于的代码窗口录入代码,否则无法触发对应的事件过程。例如工作表“生产表”的事件“Worksheet_Activate”应该置于“生产表”Excel VBA 程序开发自学通2020-2-26第 154页 /共 508页代码窗口,若置于 Thisworkbook 则无法执行。Thisworkbook 代码窗口只用于存放工作簿级别的事件。而 Excel 没有提供内置的对象来捕获应用程序级别的事件,用户必须通过类模块创建一个对象,并在类模块中调用应用程序的事件。2.不同级别事件的执行顺序事件的顺序总是遵循从低到高的规则。即如果同时设置工作表事件、工作簿事件和应用程序事件,一定最先触发工作表事件,然后是工作簿事件、应用程序事件。可以使用一个具有三个级别事件的工作簿来测试。步骤如下:(1)新建一个工作簿,将第二个工作表命名为“总表”;(2)从工作表标签处对“总表”单击右键,选择【查看代码】,从而进入其代码窗口;(3)在代码窗口录入以下工作表事件过程代码:Private Sub Worksheet_Activate()MsgBox "已通过工作表级事件激活“总表”"End Sub(4)双击 Thisworkbook 进入工作簿事件代码窗口;(5)录入以下工作簿级别的事件过程:Private Sub Workbook_SheetActivate(ByVal Sh As Object)MsgBox "已通过工作簿级事件激活“总表”"End Sub(6)单击菜单【插入】\【模块】,在新建模块中录入以下代码:Dim xlapp As New appeventsSub auto_open()Set xlapp.app = ApplicationEnd SubSub auto_close()Set xlapp.app = NothingEnd Sub(7)单击菜单【插入】\【类模块】,并按下快捷键【F4】显示属性窗口,将其默认的名称“类 1”修改为“appevents”;(8)双击进入类模块“appevents”,然后录入以下应用程序级事件过程:Public WithEvents app As ApplicationPrivate Sub app_SheetActivate(ByVal Sh As Object)MsgBox "已通过应用程序级事件激活“总表”"End Sub(9)保存工作簿代码,返回工作表界面,双击工作表名“Sheet1”进入第一个工作表,然后使用快捷键【Alt+F8】打开宏对话框,关在宏名列表中选择执行“Auto_open”;(10)此时应用程序级别的事件已经可以启动了。单击进入工作表“总表”,此时将依次弹出三个对话框,见图 8.6、图 8.7 和图 8.8 所示:图 8.6工作表事件图 8.7工作簿事件8.8 应用程序事件通过实例证明,不同级别的事件执行顺序为:工作表级——>工作簿级——应用程序级。Excel VBA 程序开发自学通2020-2-26第 155页 /共 508页3.同一级别事件的执行顺序大部分的操作都会触发多个事件,例如活动工作表是“Sheet2”时单击表名“Sheet1”,那么它可以触发“Sheet2”的“Deactivate”事件,和“Sheet1”的“Activate”事件。也可以采用前面的方法来确定多个同级别事件的执行顺序。例如新建工作表时会触发三个工作簿级别的事件。包括“NewSheet”、“SheetActivate”和“SheetDeactivate”事件。测试办法是将三个事件的代码都录入到 Thisworkbook 窗口中,然后执行一个可以触发三个事件的动作。其代码如下:Private Sub Workbook_NewSheet(ByVal Sh As Object)'创建表时执行MsgBox "新建工作表:" & Sh.NameEnd SubPrivate Sub Workbook_SheetActivate(ByVal Sh As Object) '激法表时执行MsgBox "获得焦点"End SubPrivate Sub Workbook_SheetDeactivate(ByVal Sh As Object) '失去焦点时执行MsgBox "失去焦点"End Sub当前工作簿中新建工作表时,根据提示信息可以得到事件的顺序:“NewSheet” ——>“SheetActivate” ——>“SheetDeactivate”对于其它事件,读者也可以利用以上方式进行测试,从而判断同一动作触发的多事件执行顺序。8.2.3事件的禁用与启用Excel 的事件可以手工启用和禁用。在 VBA 中,通过 Application 对象的 EnableEvents 属性来控制,将 EnableEvents属性设置为 True 即启用事件,设为 False 即为禁用事件。完整的代码如下:Application.EnableEvents = TrueApplication.EnableEvents = False通常在以下两种情况下需要禁用事件: 临时关闭事件 防止事件进入死循环1. 临时关闭事件根据需要,工作簿中可能设置了工作簿事件或者工作表事件,例如自动将录入的任何单词转换成大写。然而某些特殊情况下需要暂时禁止事件,即现在需要小写状态,那么进入 VBE 中删除代码,等后续需要时再录入代码显然非明智之举。针对这类需要临时性在禁用事件和允用事件之间切换的需求,可以使用一个专用Sub 过程来完成,且对该过程指定一个快捷键,那么在需要禁用事件时按下快捷键即可。禁用和允用事件的过程如下:Sub 允用事件()Application.EnableEvents = TrueExcel VBA 程序开发自学通2020-2-26第 156页 /共 508页End SubSub 禁用事件()Application.EnableEvents = FalseEnd Sub为过程指定快捷键的方法参见 6 章第 1 节。2. 防止事件进入死循环死循环也称无限递归,指程序无限次地调用自身。死循环的结果是系统资源耗尽或者产生“溢出堆栈空间”的运行时错误,严重影响正常工作。例如工作表中使用了 Change 事件,而且该事件过程会修改工作表中的数据,这个修改又将引发 Change 事件……如果仅有一个事件,那么通常会调用自身 97 次后停止,如果有多个事件参与,则可能进入死循环,耗尽系统资源。例如目的是在工作表中实现“不管录入什么数值都自动加 1”,代码如下:Private Sub Worksheet_Change(ByVal Target As Range)Target = Target + 1End Sub以上代码在思路上完全正确,然而因为递归引起事件的执行结果与我们的原本需求背道而驰。在单元格中录入 1,结果会显示 98,录入 100 则显示 197……如果再加入句代码,程序立即进入死循环:Private Sub Worksheet_Change(ByVal Target As Range)Target = Target + 1 '将当前单元格追加 1[a1] = Target '将当前单元格的值赋与 A1End Sub所以为了防止这种负面效应,通常在代码中修改 EnableEvents 属性来避免。且固定格式为:Private Sub Worksheet_Change(ByVal Target As Range)Application.EnableEvents = False您的代码......Application.EnableEvents = TrueEnd Sub现举一个更具有说服力的实例,假设需要在工作表“生产表”A 列中手工录入生产日期,而 B 列则使用 VBA 的事件自动产生该日期所对应的星期。按常规思路,代码如下:Private Sub Worksheet_Change(ByVal Target As Range)Target.Offset(0, 1) = WorksheetFunction.Text(Target, "DDDD")End Sub当返回工作表在 B1 录入日期“2009-5-10”后可以发现,本应在 B 列产生英文星期,结果却从 C2 到 CS2 都出现了星期“Saturday”,这是递归产生的副作用。Excel VBA 程序开发自学通2020-2-26图 8.9第 157页 /共 508页工作表事件引起的递归现象如果修改 EnableEvents 属性,合理地禁用、启用事件,那么程序完全可以达到预期效果了。代码如下:Private Sub Worksheet_Change(ByVal Target As Range)Application.EnableEvents = FalseTarget.Offset(0, 1) = WorksheetFunction.Text(Target, "DDDD yyyy 年 mm 月 dd 日")Application.EnableEvents = TrueEnd Sub本例文件参见光盘:..\ 第八章\ Chenage 事件引起的递归.xlsm8.2.4事件代码的录入方式事件过程是一个非常特殊的 Sub 过程。它的代码可以手工逐字录入,但效率极差且出错机率高。VBA 为程序员提供了一个高效而准确录入代码的便捷方式——借用对象与过程下拉框自动产生事件代码外壳。假设需要录入工作表级“Worksheet_FollowHyperlink”事件的代码,那么可以参照以下步骤:(1)从工作表标签处右键单击工作表名称,从菜单中选择【查看代码】;(2)在代码窗口顶中的“对象”下拉列表中选择“Worksheet”,此时代码窗口会产生以下代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)End Sub因为“Worksheet_SelectionChange”事件是 Excel 工作表的默认事件,只要选择对象后它就会自动产对“Worksheet_SelectionChange”事件的外壳。此时忽略它,单击右边的“过程”下拉列表,其中罗列了所有工作表事件的过程,见图 8.10 所示:图 8.10从过程下拉列表选择工作表事件过程( 3 ) 当 选 择 “ FollowHyperlink ” 过 程 名 后 , 代 码 窗 口 将 自 动 产 生“ Worksheet_FollowHyperlink ” 事 件 代 码 的 外 壳 , 此 时 再 删 除“Worksheet_SelectionChange”事件相关的代码即可。此方式录入代码时准确度比手工录入代码会高很多,特别是有很多参数的事件过程代码。例如图像控件的鼠标移过事件:Private Sub Image1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)Excel VBA 程序开发自学通2020-2-26第 158页 /共 508页End Sub8.3VBA 有哪些事件VBA 中有应用程序事件、工作簿事件和工作表事件,还有窗体、控件专用的事件。本节就 VBA 中的应用程序事件、工作簿事件和工作表事件进行介绍。窗件、控件相关的知识会在本书第 15 章进行讲述。8.3.1应用程序级别事件介绍应用程序级别的事件是 Excel 中最高级别的事件,它包括的事件相对其它任何对象的事件都更多,总共 30 个。见表 8-2 所示。表 8-2名称NewWorkbookSheetActivateSheetBeforeDoubleClickSheetBeforeRightClickSheetCalculateSheetChangeSheetDeactivateSheetFollowHyperlinkSheetPivotTableUpdateSheetSelectionChangeWindowActivateWindowDeactivateWindowResizeWorkbookActivateWorkbookAddinInstallWorkbookAddinUninstallWorkbookAfterXmlExportWorkbookAfterXmlImportWorkbookBeforeCloseWorkbookBeforePrintWorkbookBeforeSaveWorkbookBeforeXmlExport应用程序级别的事件列表说明当新建一个工作簿时发生此事件当激活任何工作表时发生此事件当双击任何工作表时发生此事件,此事件先于默认的双击操作发生。右键单击任一工作表时发生此事件,此事件先于默认的右键单击操作在重新计算工作表时或在图表上绘制更改的数据之后发生此事件当用户或外部链接更改了任何工作表中的单元格时发生此事件当任何工作表被停用时发生此事件单击Excel中的任何超链接时发生此事件在数据透视表的工作表更新之后发生此事件。任一工作表上的选定区域发生更改时,将发生此事件工作簿窗口被激活时,将发生此事件任何工作簿窗口被停用时将发生此事件任何工作簿窗口调整大小时将发生此事件当激活任一工作簿时发生此事件当工作簿作为加载宏安装时,发生此事件当任一作为加载宏的工作簿卸载时发生此事件。在Excel保存或导出指定工作簿中的 XML 数据之后发生此事件当刷新现有的 XML 数据连接或新的 XML 数据被导入任一打开的 Excel 工作簿之后,发生此事件当任一打开的工作簿关闭之前立即发生此事件在打印任一打开的工作簿之前发生此事件。在保存任一打开工作簿之前发生此事件在Excel保存或导出指定工作簿中的 XML 数据之前发生此事件Excel VBA 程序开发自学通2020-2-26WorkbookBeforeXmlImportWorkbookDeactivateWorkbookNewSheetWorkbookOpenWorkbookPivotTableCloseConnectionWorkbookPivotTableOpenConnectionWorkbookRowsetCompleteWorkbookSync第 159页 /共 508页在刷新现有的 XML 数据连接或新的 XML 数据被导入任一打开的 Excel工作簿之前,发生此事件当打开的工作簿转为非活动状态时发生此事件在任何打开的工作簿中新建工作表时发生此事件当打开一个工作簿时发生此事件在数据透视表的连接关闭之后发生此事件在数据透视表的连接打开之后发生此事件如果用户在 OLAP 数据透视表上深化记录集或调用行集操作,则会发生 WorkbookRowsetComplete 事件当作为“文档工作区”一部分的工作簿的本地副本与服务器上的副本进行同步时,发生此事件任何工作簿都可以引发应用程序级别事件。包括现在已经开启的工作簿、即将打开的工作簿以及暂时不存在(新建)的工作簿。应用程序级别的事件相比其它事件比较特殊,Excel 没有提供内置的对象来捕获应用程序级别的事件,必须声明一个对象,且将 Excel 应用程序赋值给该对象,然后在类模块中去设置程序的事件过程。在本书第 12 章中将将对应用程序级别的事件进行案例演示。8.3.2工作簿事件介绍工作簿事件是 Excel 的事件中级别居中的事件,它除了自己的事件外,子对象工作表具有的所有事件也会同时引发工作簿事件。它包括 29 个事件,见表 8-3 所示。表 8-3名称ActivateAddinInstallAddinUninstallAfterXmlExportAfterXmlImportBeforeCloseBeforePrintBeforeSaveBeforeXmlExportBeforeXmlImportDeactivateNewSheetOpenPivotTableCloseConnectionPivotTableOpenConnectionRowsetCompleteSheetActivateSheetBeforeDoubleClick工作簿级别的事件列表说明激活工作簿、工作表、图表工作表或嵌入式图表时发生此事件当工作簿作为加载宏安装时,发生此事件当工作簿作为加载宏卸载时,发生此事件在 Excel 保存或导出指定工作簿中的 XML 数据之后发生此事件在刷新现有的 XML 数据连接或将新的 XML 数据导入到指定的Excel 工作簿之后,发生此事件在关闭工作簿之前,先产生此事件。如果该工作簿已经更改过,则本事件在询问用户是否保存更改之前产生在打印指定工作簿(或者其中的任何内容)之前,发生此事件。保存工作簿之前发生此事件在 Excel保存或导出指定工作簿中的 XML 数据之前发生此事件在刷新现有的 XML 数据连接或将新的 XML 数据导入到指定的Excel工作簿之前,发生此事件图表、工作表或工作簿被停用时发生此事件当在工作簿中新建工作表时发生此事件打开工作簿时,发生此事件数据透视表关闭与其数据源的连接后发生此事件数据透视表打开与其数据源的连接后发生此事件如果用户在 OLAP 数据透视表上深化记录集或调用行集操作,则会引发此事件当激活任何工作表时发生此事件当双击任何工作表时发生此事件,此事件先于默认的双击操作发生Excel VBA 程序开发自学通SheetBeforeRightClickSheetCalculateSheetChangeSheetDeactivateSheetFollowHyperlinkSheetPivotTableUpdateSheetSelectionChangeSyncWindowActivateWindowDeactivateWindowResize2020-2-26第 160页 /共 508页右键单击任一工作表时发生此事件,此事件先于默认的右键单击操作在重新计算工作表时或在图表上绘制更改的数据之后发生此事件当用户或外部链接更改了任何工作表中的单元格时发生此事件当任何工作表被停用时发生此事件单击Excel 中的任何超链接时发生此事件在数据透视表的工作表更新之后发生此事件任一工作表上的选定区域发生更改时,将发生此事件当作为“文档工作区”一部分的工作表的本地副本与服务器上的副本进行同步时,发生此事件工作簿窗口被激活时,将发生此事件任何工作簿窗口被停用时将发生此事件任何工作簿窗口调整大小时将发生此事件工作簿事件级别低于应用程序的事件,只有当前工作簿才可以触发工作簿中的事件代码。工作簿事件主要包括三个大类: 工作簿开启、关闭时引发事件 工作簿中任意一个工作表、透视表引发事件 工作簿窗口的变化引发事件在工作中常用的是前两类,在本书第 12 章中将对工作簿事件进行案例演示。8.3.3工作表事件介绍工作表事件是最底层的事件,它只有 9 个事件,见表 8-4 所示。表 8-4名称ActivateBeforeDoubleClickBeforeRightClickCalculateChangeDeactivateFollowHyperlinkPivotTableUpdateSelectionChange工作表级别的事件列表说明激活工作簿、工作表、图表工作表或嵌入式图表时发生此事件当双击工作表时发生此事件,此事件先于默认的双击操作右键单击工作表时发生此事件,此事件先于默认的右键单击操作对于 Worksheet 对象,在对工作表进行重新计算之后发生此事件当用户更改工作表中的单元格,或外部链接引起单元格的更改时发生此事件图表、工作表或工作簿被停用时发生此事件当单击工作表上的任意超链接时,发生此事件工作簿中的数据透视表更新后发生此事件当工作表上的选定区域发生改变时发生此事件只能存放代码的工作表才可以触发工作表事件,如果需要事件过程对任何工作表都生效,请用工作簿事件。在使用工作表事件时需要特别注意的是要防止事件过程进入无限递归。在本书第 12 章中将将对工作表事件进行案例演示。8.3.4事件的特例所谓特例,是指按照常规思路,大家认为可能触发事件的操作,它却没有触发事件;而大家认为可能不会触发事件的动作,事实却引起了事件过程的执行。现例举六个这类特例:Excel VBA 程序开发自学通2020-2-26第 161页 /共 508页1.删除空文本时触发“Worksheet_Change”事件“Worksheet_Change”事件是指工作表中任意单元格的内容发生改变时所触发的工作表事件。然而在 A1 为空白的状态下按下键上的【Delete】键也可以执行该事件。例如:Private Sub Worksheet_Change(ByVal Target As Range)MsgBox "您在修改:" & Target.Address(0, 0)End Sub当用户删除本身为空白的 A1 时,将会弹出图 8.11 所示提示信息:图 8.11 删除空单元格触发“Worksheet_Change”事件2.插入批注时不触发任何事件任何单元格插入批注时都不触发任何事件,这一点需要记住。可以理解为批注就是一个悬浮于单元格之上的图形对象,导入图形不改变单元格数据。3.修格单元格格式不触发事件不管对单元格设置任何格式,包括背景色、填充图案、对齐方式及边框,定义数字格式等等都不触发任何事件。虽然定义数字格式时单元格的显示内容发生了改变,然后单元格的文字并未变化,即 Range 的 Text 属性未被用户修改。4.清除格式会触发“Worksheet_Change”事件更为怪异的是清除单元格格式时会触发“Worksheet_Change”事件。我们无法清楚微软当初的设计意图,为什么清除格式这种不修改单元格 Text 属性的操作会触发工作表事件,只要记住这个特例即可。5.数据分列时不触发事件假设 A1 单元格中的文本字符串为“中国/广东/广州”,将其按分割符“/”进行分列,见图 8.12 所示。在分列时不触发任何事件,包括“Worksheet_Change”事件和“Worksheet_SelectionChange”事件。Excel VBA 程序开发自学通2020-2-26第 162页 /共 508页图 8.12 数据分列6.表单控件修改数据时不触发任何事件表单控件中的复选框、列表框和组合框等等都可以修改单元格的值,但是经过多次测试它不会触发任何事件。但是 ActiveX 控件中的复选框、列表框和组合框却可以触发工作表事件、工作簿事件和应用程序事件。所以如果需要工作表事件能够监视控件修改单元格的值,应该使用 ActiveX 控件,而不是表单控件。Excel VBA 程序开发自学通2020-2-26第九章第 163页 /共 508页VBA 程序常规则在编写 VBA 程序时需要遵循很多规则,才可以更有效率地开发程序,才能让他人更清晰地看明白代码的设计意图与思路,才可能方便自己修改代码……这些规则都不是强制性的,甚至都是不成文的,只是一些先驱们的经验积累,到最后就成为不成文的默认规则。遵循这些规则,对于一个程序员来说至关重要。本章要点: 代码编写规则 优化代码9.1 代码编写规则本节介绍在代码编写过程的中一些常规则。9.1.1对代码添加注释不管程序开发人员和终端用户是否同一个人,都完全有必要对代码进行注释。即开发的代码属于自用型或者转给其他用户使用,都需要让代码具有清晰的含义注释与思路注释,才有利于阅读和修改。1.添加注释的作途添加代码注释主要有三个作用: 每句代码的含义注释说明当前代码的含义,让阅读者明白代码思路。 整个程序的声明注释通常对于大中型插件,需要声明开发者姓名、版权、版本号、开发日期、更新日期、更新内容及本程序功能说明、注意事项等等,有利于用户了解各版本功能及差异及使用时的注意事项。 调试代码在编写代码时,需要对每句代码进行多次调试,直到完全满意才会投入工作中实际应用。在调试时,某些代码需要暂时禁止执行,而测试其它代码,那么此时最好的方式就是注释代码。2.添加注释的方法对代码添加注释有三种方式:Rem 和单引号(也称撇号),以及工具按钮法。(1)利用 Rem 添加程序注释Rem:用来在程序中添加注释。其语法如下:Rem commentVBA 对于注释行自动以绿色呈现,以示区别。例如以下代码中,在过程上一行即为注释,阐述当前过程的功能。Excel VBA 程序开发自学通2020-2-26第 164页 /共 508页Rem 程序功能:获取 Excel 用户名称Sub UserName()MsgBox Application.UserNameEnd Sub在任何代码窗口录入以上代码,Rem 注释行都呈绿色显示,见图 9.1 所示。图 9.1 为过程添加功能注释如果需要对某行代码进行注释,有两种方式:注释在 VBA 代码上方及右方。当需要注释置于代码上方时,录入注释后再在次行录入代码。见图 9.2 所示:图 9.2 为代码添加含义注释也可以仅仅对重要的代码或者不容易明白含义的代码进行注释,以缩减程序的大小。如果需要在过程右方添加注释,使代码看起来更紧凑,则需要借用冒号来完成。例如:Sub UserName(): Rem 程序功能:获取 Excel 用户名称MsgBox Application.UserName: Rem 利用 UserName 属性获取用户名称End SubVBA 仍然将注释部分绿色显示,和代码加以区别,见图 9.3 所示:Excel VBA 程序开发自学通2020-2-26第 165页 /共 508页图 9.3 在代码右方添加含义注释注意“Rem 和注释之间必须有空格,否则将产生编辑错误。(2)利用半角单引号添加程序注释在使用效率上,本方法较 Rem 法更简单。直接在注释前添加一个半角单引号即可。在引号与注释之间不需要空格。和 Rem 法一致,也可以在代码的上方或者右方添加注释。例如获取今天日期及星期的过程:Sub 今天()MsgBox Format(Date, "AAAA")MsgBox Format(Date, "yyyy 年 mm 月 dd 日")End Sub添加注释后可以有两种效果,见图 9.4 所示:图 9.4 利用单引号对代码添加注释在添加注释时,如果注释与代码呈上下行关系者,则注释尽量与代码对齐。如上图中第一个过程,过程名称未缩进,那么代码的注释也不需要缩进;过程中间的代码有缩进四个单位,那么注释也相应地缩进四个单位。如果代码与注释呈前后关系,则在代码与注释之间应添加几个空格。虽然以上规则并非必须执行,但遵循本规则会使代码美观,给阅读者带来便利。(3)用工具栏按钮批量注释代码在“编辑”工具栏中有一个专用于“设置注释块”和“解除注释块”的按钮,分别用于批量添加注释及批量解除注释。图 9.5 中鼠标指向的图标即为设置注释专用工具,其右边第一个按钮则用于解除注释。Excel VBA 程序开发自学通2020-2-26第 166页 /共 508页图 9.5 设置注释及解除注释的按钮使用工具设置注释的步骤为:选择代码(可以是多行,甚至多个过程)单击“设置注释块”按钮。该工具按钮添加注释是以行为单位,无法从代码中间某个字符开始设置注释。所以选择代码时只需要定位于该行任意位置再单击按钮“设置注释行”即可。例如以下过程,获取今天的日期提供了三种方式,现暂时仅需第三种,但不除排以后使用前两种格式的可能。那么删除前两行代码显示是不可取的,注释前两行使其暂是不运行,需要时再解除注释才是最佳选择。Sub 今天()MsgBox Format(Date, "DDDD yyyy 年 mm 月 dd 日")MsgBox Format(Date, "DDD yyyy 年 mm 月 dd 日")MsgBox Format(Date, "AAAA yyyy 年 mm 月 dd 日")End Sub注释前两行时, 鼠标选择两行代码的任意位置再单击“设置注释块”均可,例如图 9.6。图 9.6 批量设置注释后续需要该代码时,不再重新编写代码,选择目标代码行的任意位置,然后单击“解除注释块”即可。提示:批量设置注释块时,如果需要注释掉整个过程,那么需要选择过程的所有行再执行,不可单独留任意一行代码,否则可能产生编译错误。3.设置注释在调试代码中的作用调试代码是每个程序员的必然经历。鉴于测试环境或者数据与实际工作中可能存在的差异,那么调试代码时部分代码不需要它执行,但在调试时又不能直接删除它。而在代码前加一个单引号使其不执行才是上策。Excel VBA 程序开发自学通2020-2-26第 167页 /共 508页例如以下代码用于检测 B2:B10000 的成绩,如果成绩单元格为空白则提示用户录入成绩,如果成绩小于 60 分则对该单元格填充红色背景。Sub 标示小于 60 分的成绩()For i = 2 To 10000 '遍历 B2:B10000IF Len(Cells(i, 2)) = 0 Then MsgBox Cells(i, 1).Address(0, 0) & "没有录入成绩"IF Cells(i, 2) <610 Then Cells(i, 2).Interior.ColorIndex = 3NextEnd Sub在测试时,往往随意录入几个数据,而造成 B2:B10000 区域中存在大量的空白单元格,那么调试此代码时将弹出无数个对话框。为了避免此问题,可以将代码中选择第三句设置为注释,暂时不执行该语句,在正式投入工作中使用时再解除注释即可。另外,对于调试具有破坏型的代码时也有必要将部分代码禁用,测试其它代码。例如批量清除数据、图片或者批量添加条件格式等等。4.对插件添加声明对于大中型插件或者其它成品软件,有必要对工具进行说明,包括功能、版本、更新项目等等。通常置于程序最顶端。例如双身份证号码获取信的插件,可以在前面加入一些信息。见图 9.7 所示:图 9.7 对身份证函数插件添加功能与版本声明 1也可以采用以下方式对程序进行说明:Excel VBA 程序开发自学通2020-2-26第 168页 /共 508页图 9.8 对身份证函数插件添加功能与版本声明 2在对工具添加说明注释时,其实有必要进行一些美化,让终端用户感觉更赏心悦目。况且程序的美化与程序完善是没有任何冲突的。在本书第 29 章将带领大家设计一个美化 VBA 代码的工具,提供一些可选的美化注释,用户仅仅需要单击即可完成上述美化注释。9.1.2长代码分行VBA 代码每个允许存放 1 到 1024 个字符。然而为了提升阅读和修改的便利性,应尽量不要超过 200 个字符,或者尽量让一行代码不超过当前屏幕的可见宽度,使用户查看代码时不需要移动滚动条。对代码进行分行,其方法如下:从整行代码截断点处插入空格,再录入下划线“_”,最后将单击回车键,使后面的内容置于第二行。假设将整行代码包括“AAAABBBB”A,需要在第四个“A”之后截断两行,那么代码截前后的对比图见图 9.9 所示。图 9.9 代码换行通常需要换行的代码都是带有较长文本、具于说明性质的消息框。例如以下用于获取磁盘信息的代码:Sub 磁盘信息()Dim 盘符 As String, 类型 As StringFor i = 1 To 26Excel VBA 程序开发自学通2020-2-26第 169页 /共 508页On Error Resume Next盘符 = Mid("ABCDEFGHIJKLMNOPQRSTUVWXYZ", i, 1)Select Case CreateObject("Scripting.FileSystemObject").GetDrive( 盘 符&":").DriveTypeCase 0: 类型 = "无法识别"Case 1: 类型 = "移动磁盘"Case 2: 类型 = "固定磁盘"Case 3: 类型 = "网络磁盘"Case 4: 类型 = "光盘 DVD"Case 5: 类型 = "虚拟磁盘"End SelectIF Err.Number 68 Thenmsg = msg & CreateObject("Scripting.FileSystemObject").GetDrive( 盘 符 &":").DriveLetter&""&类 型&""&CreateObject("Scripting.FileSystemObject").GetDrive(盘符 & ":").SerialNumber & ""&CreateObject("Scripting.FileSystemObject").GetDrive(盘符 & ":").TotalSize / 1024 & ""& CreateObject("Scripting.FileSystemObject").GetDrive( 盘 符 & ":").FreeSpace / 1024 &Chr(10)End IFNext iMsgBox msgEnd Sub代码的含义在本书第 19 章将进行详述,此得仅仅需要理解长代码如何分行显示。本例的代码中,Msgbox 语句的代码过长,超过两屏的宽度才能显示完成,这非常不利于阅读。可以通过以下方式截成五行:msg = msg & _CreateObject("Scripting.FileSystemObject").GetDrive(盘符 & ":").DriveLetter & ""& 类型 &""&_CreateObject("Scripting.FileSystemObject").GetDrive(盘符 & ":").SerialNumber & ""&_CreateObject("Scripting.FileSystemObject").GetDrive(盘符 & ":").TotalSize / 1024 & ""&_CreateObject("Scripting.FileSystemObject").GetDrive( 盘 符 & ":").FreeSpace / 1024 &Chr(10)成五行后的代码不仅看方便,对于理解代码的含义也有益处。其中第二行开始,每行获取一个磁盘的一类信息。第二行 DriveLetter 表示卷标,第三行 SerialNumber表示序列号……本例文件参见光盘:..\ 第九章\代码分行.xlsm如果需要从一个字符串中间截成两行,那么需要对截断后的两段字符串补齐双引号,且添加连接符。例如:Sub test()MsgBox "123456789123456789"End Sub本过程中 Msgbox 语句截断为两行后,效果如下:Sub test()MsgBox "123456789" _& "123456789"End Sub如果双引号不配对,将产生编辑错误。Excel VBA 程序开发自学通9.1.32020-2-26第 170页 /共 508页代码缩进对齐代码缩进是指在代码的前面根据其层次不同,添加不同数量的空格,或者使用 Tab键缩进若干个单位。代码缩进也不是必须的,但是对于阅读代码却有较大的帮助。所以在程序开发中,应尽量对代码进行缩进,使其更具有层次感。例如图 9.10,使用缩进功能后对于理解代码含义存在积极作用:图 9.10 代码缩进代码缩进有两种方式:手动缩进一行和利用工具批量缩进1.单行代码手动缩进如果是单行代码需要缩进,或者有多行代码需要缩进,但是缩进的单位不一致,那么可以手动对各行逐一缩进。缩进的方式为:将光标定位于代码前面,按下 Tab 键,默状态下一次缩进 4 个单位。用户可以通过“选对”对话框“编辑器格式”中的“Tab 键宽度”来指定这个缩进单位。2.利用工具栏按钮批量缩进VBE 中的“编辑菜单”有一个“缩进”和一个“突出”按钮,它们都可以对选择的代码行进行批量操作。如果需要多行代码缩进相同宽度,那么应该使用本“缩进”按钮来完成;如果需要将多行代码取消缩进,那么应该使用“突出”按钮来完成。用以下代码为例,将其缩进为图 9.10 之样式:Sub 生成菜单()With Application.CommandBars(1).Controls.Add(msoControlPopup, 1, , 3, 1).Caption = "我的菜单(&M)"With .Controls.Add(msoControlButton, 1, , , True).Caption = "菜单一(&One)…".OnAction = "one".Style = msoButtonIconAndCaption.FaceId = 225Excel VBA 程序开发自学通2020-2-26第 171页 /共 508页End WithWith .Controls.Add(msoControlButton, 1, , , True).Caption = "菜单二(&Two)".OnAction = "Two".Style = msoButtonIconAndCaption.FaceId = 300End WithEnd WithEnd Sub缩进步骤为:(1)选择第 5 到 8 行,并单击工具栏的“缩进”;(2)选择第 11 到 14 行,并单击工具栏的“缩进”;(3)选择第 3 到 15 行,并单击工具栏的“缩进”;(4)选择第 2 到 16 行,并单击工具栏的“缩进”;最后的效果即为图 9.10 所示。如果需要所有代码左对齐,从向实现代码的减肥,那么可以选择所有代码后,多次单击“突出”按钮,直到所有代码左对齐。本例文件参见光盘:..\ 第九章\代码缩进.xlsm9.1.4声明有意义的变量名称在本书第 5 章中已经讲述关于声明变量的好处,事实上变量的名称也需要进行规范。虽然“ABC”或者“One”、“数量 1”等等都是合法变量名,然而对于用户理解代码含义却带来障碍。而正确的命名方式通常有三种: 以数据类型简写为准例如声明一个 String 型变量可以使用以下方式:Dim str as string此方式的优点是不管在何处看到本变量就立刻明白它的数据类型是 String,用于文本字符串。而声明一个 Integer 型的变量则可以使用以下方式Dim inte As Integer而声明一个工作表对象变量则用以下方式:Dim sht As Worksheet 以变量作用进行简要描述本方式则利用变量的作途来做为变量名的参考。例如需要遍历工作表中所有图形对象,那么对变量名声明为“图形计数”或者“Shape_count”等等,熟悉英文就是英文对变量进行描述,否则用中文。Sub 图形左对齐()Dim 图形计数 As IntegerFor 图形计数 = 1 To ActiveSheet.Shapes.CountActiveSheet.Shapes(图形计数).Left = 0NextEnd Sub 两者综合为了让他人对代码中的变量更利于理解,也可以将两种方式结合来命名变量。例如“图形左对齐”过程,可以使用以下方式声明变量:Excel VBA 程序开发自学通2020-2-26第 172页 /共 508页Dim Shape_int As Integer其中前一段 Shape 表示这是一个用于图形对象的变量,后一段 Int 则表示它的数据数型是 Integer。而两段之间用下划线“_”连接便于区分。本方式优势在于可以准确、迅速理解变量的含义,缺点是变量名长。读者可以根据自己的喜好在三种方式中选择。9.1.5IF…end if 类配对语句的录入方式VBA 中有很多类似于 IF …End if 这类需要配对的语句。例如With…End With、For each…Next、For…next、Do…LoopVBA 可以对“Sub”配对,全自动生成“End Sub”,但以上几种语句却需要开发者自己录入完整。对于初学者,常常遇到图 9.11 和图 9.12 所示编辑错误,笔者也不例外。在经历过无数次烦恼后决定改变代码录入方式,从而彻底杜绝这种错误。图 9.11 IF 语句录入不完整图 9.12 For 语句录入不完整例如 IF 语句,其正确的录入步骤是:(1)录入“IF…Then”;(2)单击两次回车键后录入“End IF”;(3)按下上箭头返回第二行,接着录入其它代码。简单的说就是将“IF”和“End if”中间的代码与“End if”交换录入顺序,从而确保不会因忘记录入“End if”而产生编译错误。对于其它需要配对的代码同样如此。9.1.6录入事件代码的方式对于 VBA 中所有事件过程的代码,尽量通过对象和过程下拉列表来录入 Sub 过程的程序外壳。包括工作表事件、工作簿事件、以及窗体事件、窗体中所有控件的事件。部分过程有多个参数,在录入代码时很难把握准确,而从下拉列表选择则快速无误。图 9.13 是窗体中选择方式录入“CommandButton1_BeforeDropOrPaste”事件的截图。Excel VBA 程序开发自学通2020-2-26第 173页 /共 508页图 9.13 通过下拉框输入事件过程9.1.7借用自动列出程序录入代码VBA 中数千个属性与方法,任何人都无法准确地记得所有单词。而手工录入语句则产生误差的机率将非常大,而且速度较慢。借用“自动列出成员”功能有利于准确录入代码。录入调用激法程序的方法“AppActivate”因单词较长,不利于记忆,也不便于输入,那么仅仅实际工作中仅仅知道它是“APP”开头即可,其它字符可以借用“自动列出成员”来完成。方法如下:(1)录入“VBA.”,注意使用半角状态下的小圆点,此时将弹出一个下后列表供用户选择,见图 9.14 所示。其中“VBA”是 AppActivate 的库,而 ScreenUpdating的库则是“Application”;(2)移动下箭头,当移到目标上之后按下快捷【Tab】即可录入完整代码。图 9.14 VBA 对象的所有方法与属性列表如果需要录入在窗体中录窗体的各种属性或者控件名称,则可以使用“Me.”来产生下拉列表;而需要录入应程序程的各种属性时,则改用“Application.”,VBA 会列出所有 Application 的成员列表用户选择。如果所有属性、方法、子对象都采用此方式输入,那么在程序代码中将会产生很多“Me.”或者“VBA.”,而事实上删除这些代码仍然可以正常执行,而且速度更快。所以在程序代码编写完成后,可以批量替换掉所有不需要的对象库。例如程序中有多个“Me.”,那么批量替换步骤为:(1)按下快捷键【Ctrl+H】打开“替换”对话框;(2)将“查找内容”设定为“Me.”,将“替换为”目标保持空白即可;(3)勾选“全字匹配”,然后再单击“全部替换”按钮即可替换掉所有“Me.”,其方对象库的替换方式相同。替换对话框的设置见图 9.15 所示Excel VBA 程序开发自学通2020-2-26第 174页 /共 508页图 9.15 设置替换内容9.1.8善用公共变量如果过程中的运算结果需要在过程 2 中调用,那么可以将过程 1 的运算结果赋与某个辅助单元格或者写入注册表中,在过程二中则调用这个辅助单元格的值或者读取注册表。虽然以上方式完全可行,但是需要操作单元格对象或者注册表对象,在执行效率上不太理想。而最佳方式是借用公式变量做为过渡,让过程二直接调用变量即可。Public Sums As LongSub 过程一()Rem 汇总工作表Sums = WorksheetFunction.Sum(Sheets(1).UsedRange)End SubSub 过程二()Rem 调用公共变量MsgBox SumsEnd Sub在以上代码中,执行过程一后,变量 Sums 即已赋值,只要工作表不关闭,那么任何模块中都可以直接调用 Sums 的值,相比过程一中将汇总值存入单元格,过程二再读取单元格的值要快得多。9.1.9将较大的过程分为多个再调用VBA 允许一个过程存入上千条代码,然而一个过程太庞大不利于阅读和维护,特别是一个代码超过一屏时。此时需要将过程按作用分为多个子过程,再到主过程中调用。Sub 主程序()MsgBox "1"MsgBox "2"MsgBox "3"End Sub假设以上过程需要分为多个子过程,那么可以按以下方式进行:Sub 主程序()Call 过程一Call 过程二Call 过程三Excel VBA 程序开发自学通2020-2-26第 175页 /共 508页End SubSub 过程一()MsgBox "1"End SubSub 过程二()MsgBox "2"End SubSub 过程三()MsgBox "3"End Sub但是对于一个横跨两屏的 With 语句或者 IF 语句则不宜截断。例如“With”在第一屏中,而“End with”在第二屏,则不宜将 With 语句分为两个过程。9.1.10 减少过程参数VBA 中的 Sub 过程和 Function 过程都支持 200 个以上的参数,但为了便于维护和理解,自定义函数尽量不要超过 5 个必选参数。9.1.11兼容 Excel 2007 和 Excel 2003目前阶段,Excel 2003 和 Excel 2007 用户并存,编写代码时应该随时考虑代码的兼容性。兼容性主要体现在三个方面:方法与属性的增减、工作表行列数差异、菜单与功能区模式1.对象、方法与属性与函数的增减在 Excel 2007 中,相对 Excel 2003 增加了很多对象、方法、属性及函数,如果代码中涉及这些属性,那么在低端用户系统中执行代码就一定出错。例如 Excel 2007 独有的色阶条件格式、SmartArt、工作表函数 Sumifs 等等。再如排序功能,Excel 对它做了强化,可以添加 3 个以上的条件。如果需要体现程序的兼容性时应该使用 3 个以内的排序条件。同时,Excel 2007 相对于 Excel 2003 也精简掉部分功能,或者对原有功能进行了部分修正。例如 FileSearch 功能已删除,针对以上兼容性问题,编写代码时就尽量使用 Excel 2003 和 2007 都支持的对象、属性、方法或者函数。例如 FileSearch 不支持2007,但是 Dir 函数却是 Excel 2007 和 Excel 2003 通用,那么可以使用 Dir 取代FileSearch。再如单元格的字符容量在 Excel 2003 和 Excel 2007 中也差异较大,此类情况应以最低端容量做为标准,才可以使程序有更好的兼容性。2.工作表行列数差异Excel 2003 的最大行数为 65536,最大列数为 256;而 Excel 2007 的最大行数为1048576,最大列数为 16384。那么在编写代码时引用最后一行或者最后一列时不能采用以下两种形式:[A65536]——不适用于 Excel 2007Excel VBA 程序开发自学通2020-2-26第 176页 /共 508页[A1048576]——不适用于 Excel 2003甚至利用版本号来判断也是无法达到通用的,例如:Range("A" & IIF(Application.Version = 12, 1048576, 65536))以上代码引用 A 列最后一个单元格,虽然利用 Version 来判断版本号,对 Excel2007 使用 1048576,对 Excel 2003 使用 65536,但仍然忽略了一个重点:Excel 可以使用兼容模式。而最精典的引用是:Cells(Rows.Count, 1)——A 列最后一个单元格Cells(1, Columns.Count)——第一行最后一个单元格Cells(1, Columns.Count).EntireColumn——最后一列Range(Rows.Count & ":" & Rows.Count)——最后一行3.菜单与功能区模式Excel 2007 采用新的功能区,与以往任何版本的 Office 软件菜单在外观上或者代码编写上都完全不同。Excel 2003 的 CommandBars 对象虽然被 Excel 2007 保留了下来,但其 Position 属性不再发生任何作用,即不管如何设置,自定义工具条都显示在功能区中,而不会像Excel 2003 一样可以在屏幕上、下、左、右任意切换。为了让程序的兼容性更好,尽量使用菜单,而非功能区或者工具条。9.2 优化代码VBA 爱好者都追求所有代码可以正确执行,准确地获取需求的结果。然而要做一个专业的程序员则不能止于准确,而需要追求高效。本节介绍一些优化代码、提升速度的方法。9.2.1强制声明变量VBA 并不要求用户必须声明每个变量,VBA 会自动为每个变量分配数据类型。这是相对于其它程序软件的一个优点,即兼容性好。然而同时也是一个缺点,不声明变量其类型时程序在执行时将会消耗更多的内存。相当于牺牲效率换取兼容性。为了提升程序的效率,在编写代码时,应尽量将所有变量显示声明,除非某个变量的类型无法把握(初学者较常见)。9.2.2善用常量如果某个数值或者字符串在程序中反复出现,那么尽量声明一个常量做取代该值,将后在代码中直接调用常量。Excel VBA 程序开发自学通9.2.32020-2-26第 177页 /共 508页关闭屏幕更新在单元格中写入数据或者批量插入图形对象时,每执行一句代码屏幕会更新一次,而更新屏幕需要时间。在大多数情况下,完全没有必要更新屏幕的状态。开发者可以关闭屏幕更新来提升效率,等所有过程执行完毕后再恢复屏幕更新即可。VBA 提供了一个可以控制屏幕更新开、关的属性:ScreenUpdating。它的语法如下:Application.ScreenUpdating=True/False如果将该属性值设为 True 则允许屏幕更新,否则禁止屏幕更新。下面举例证明。以下过程为隐藏 Excel 2007 所有偶数列:Sub 隐藏偶数列()Dim Col As Integer, Tim As LongTim = TimerFor Col = 1 To Columns.CountIF Col Mod 2 = 0 Then Cells(1, Col).EntireColumn.Hidden = TrueNextMsgBox "程序共运行了" & Format(Timer - Tim, "0.00") & "秒"End Sub在笔者的电脑上,该过程的执行时间是 46 秒,如果用 Excel 2003 或者 Excel 2007在兼容模式下使用,那么因为列数仅仅 256 列,时间会大大缩短。如果在程序中关闭屏幕更新,那么代码的效率将大大提高。Sub 隐藏偶数列()Dim Col As Integer, Tim As LongApplication.ScreenUpdating = FalseTim = TimerFor Col = 1 To Columns.CountIF Col Mod 2 = 0 Then Cells(1, Col).EntireColumn.Hidden = TrueNextApplication.ScreenUpdating = TrueMsgBox "程序共运行了" & Format(Timer - Tim, "0.00") & "秒"End Sub在笔者的电脑上,该过程的执行时间是 1.6 秒,与前一个未关闭屏幕更新的过程相比,在效率上提高了 40 多倍。“Application.ScreenUpdating = False”这一句代码通常需要放置于循环语句之前,在循环完成后再恢复屏幕更新,否则会影响正常工作。本例文件参见光盘:..\ 第九章\隐藏偶数列.xlsm9.2.4利用 WITH 减少对象读取次数VBA 中读取对象需要花费一定的时间,而且对于多级对象(同时列出父对象与子对象)时需要的时间更长。例如以下两句代码:[a1]ThisWorkbook.Sheets(1).[a1]虽然它们都指向同一个单元格对象,对 A1 读写后的结果也完全一致,然而前者圆点更少,读取时间也相应更少。在 VBA 中引用对象时,每出现一个圆点就需要去读取一个对象,通过以下两段代码可以比较出“[a1]“和“ThisWorkbook.Sheets(1).[a1]”Excel VBA 程序开发自学通2020-2-26第 178页 /共 508页在效率上的差异:Sub 循环 10000 次 1()Dim tim As Longtim = TimerFor i = 1 To 10000[a1] = iNext iMsgBox "程序共运行了" & Format(Timer - tim, "0.00") & "秒"End Sub以上代码在笔者的电脑上执行时间是 5.44 秒。Sub 循环 10000 次 2()Dim tim As Longtim = TimerFor i = 1 To 10000ThisWorkbook.Sheets(1).[a1] = iNext iMsgBox "程序共运行了" & Format(Timer - tim, "0.00") & "秒"End Sub以上代码在笔者的电脑上执行时间是 6.45 秒。虽然在不同的电脑上执行时间会有所区别,但是第二个过程的执行时间长于前一个过程的时间却是一定的。With 语句正是解决这类问题的,本书第 7 章中已讲述利用 With 来简化对象的引用次数,而引用次数减少的同时,也提高了代码的执行效率。利用 With 来简化对象读取及提高执行效率,可以从以下案例体现:Sub 设置字体()Range("A1").Font.Name = "黑体"Range("A1").Font.FontStyle = "加粗 倾斜"Range("A1").Font.Size = 11Range("A1").Font.Underline = xlUnderlineStyleNoneRange("A1").Font.Color = 192End Sub以上代码中需要引用单元格对象和字体对象(Font)五次。Sub 设置字体()With Range("A1").Font.Name = "黑体".FontStyle = "加粗 倾斜".Size = 11.Underline = xlUnderlineStyleNone.Color = 192End WithEnd Sub以上代码需要引用单元格对象和字体对象(Font)一次。如果单独执行以上两段代码,在执行效率上可能是无法感觉到,然而在一个大中型程序中,多段代码综合后,每句代码的小小差异累积起来就会对程序较大的影响了。所以在编代码时有必要对每个细节进行优化。9.2.5利用变量减少对象读取次数如果某个变量在一个过程中多次出现,应考虑用一个变量来替换该对象。因为变量存在内存中,VBA 读取内存数据远远快于对象。Excel VBA 程序开发自学通2020-2-26第 179页 /共 508页例如以下代码:Sub 对小于 B1 的单元格填充背景 1()Dim tim As Long, rng As Rangetim = TimerFor Each rng In Range("A1:A20000")IF rng > [b1] Then rng.Interior.ColorIndex = 3Next[B2] = Format(Timer - tim, "0.00") & "秒"End Sub在代码中,单元格 B1 被引用了 20000 次,程序的执行时间在笔者的电脑上大概2.4 秒钟。Sub 对小于 B1 的单元格填充背景 2()Dim tim As Long, rng As Range, 标准 As Bytetim = Timer标准 = [b1]For Each rng In Range("A1:A20000")IF rng > 标准 Then rng.Interior.ColorIndex = 3Next[B3] = Format(Timer - tim, "0.00") & "秒"End Sub在修改后的代码中,单元格 B1 仅仅需要读取一次。在后面的循环中,单元格 A1不再参与运算,而是内存中的变量“标准”在参与运算。该代码的执行时间少于 1 秒钟。本例文件参见光盘:..\ 第九章\对小于 B1 的单元格填充背景.xlsm9.2.6善用带$的字符串处理函数在 VBA 中,有两套字符串处理函数,包括带“$”和不带“$”的函数,例如 Mid和 Mid$,Left 和 Left$,Right 和 Right$。如果使用不带“$”符号的函数计算字符串,那么 VBA 将字符串作为变体型数据进行计算,而使用带“$”的函数时则将字符串当做 String 类型进行计算。显示变体型数据在计算时需要更多的内存空间。例如以下两句代码,第二句在执行效率上会稍占优势:Reault = Mid("中华人民共和国", 3)Reault = Mid$("中华人民共和国", 3)9.2.7善用循环中的步长减少循环次数当使用有针对性的 For 循环,即仅仅需要对循环对象中的部分对象进行操作时,应该调整循环的步长来减少循环的次数。例如将奇数行添加背景色,步长为 1 的 For 循环代码如下:Sub 前 10000 行背景着色()tim = TimerFor i = 1 To 100000IF i Mod 2 = 1 Then Cells(i, 1).EntireRow.Interior.ColorIndex = 15Next iExcel VBA 程序开发自学通2020-2-26第 180页 /共 508页MsgBox Format(Timer - tim, "0.00") & 秒End Sub本代码需要循环的次数是 10000 次Sub 前 10000 行背景着色 2()tim = TimerFor i = 1 To 100000 Step 2Cells(i, 1).EntireRow.Interior.ColorIndex = 15Next iMsgBox Format(Timer - tim, "0.00") & 秒End Sub本代码需要循环的次数是 5000 次,其执行结果与前一个过程完全一致。读者可以分别执行两个过程测试其时间。9.2.8利用数组代替单元格对象VBA 处理数组的速度远远大于处理对象的速度,对于可以利用数组来替换对象的都尽量使用数据,例如 1000 个图片的名称,或者 1000 个单元格,或者不确定个数的工作表名称。关于数组的概念和具体应用,在本书第十三章和第十四章将进行详述,此处仅仅需在了解数组的处理速度快于对象即可。下面举两个实例,证实数组的字符处理方面的优势。实例一:对 3000 个学生中不及格成绩标示“不及格”实现此功能可以两种方式,包括使用数组及非数组。Sub 对小于 60 分成绩进行注释()Dim i As Integer, tim As Long '声明变量tim = Timer '获取当前时间'从 2 开始至最后一个非空单元格结束For i = 2 To Cells(Rows.Count, 1).End(xlUp).Row'如果小于 60 则在右边单元格标注“不及格”IF Cells(i, 2) < 60 Then Cells(i, 3) = "不及格"Next i'报告时间MsgBox Format(Timer - tim, "0.00") & 秒End Sub该过程会对 3000 个学生的成绩(假设工作表中是 3000 个成绩)逐一进行判断,再将小于 60 的成绩对应的单元格写入“不及格”,它需要读取单元格 3000 次,写入单元格的次数随不及格人数而定。Sub 对小于 60 分成绩进行注释 2()Dim i As Integer, tim As Long, arr1(), arr2() '声明变量,包括两个数组变量tim = Timer '获取当前时间'将成绩赋与数组变量,后续读取时不再读单元格,而是从内存中取值arr1 = Range([B2], Cells(Rows.Count, 2).End(xlUp))'重置第二个数组变量大小ReDim arr2(1 To UBound(arr1), 1 To 1)'循环数组For i = 1 To UBound(arr1)'如果数组中某元素小于 60 则对第二个数组对应的值赋值为“不及格”IF arr1(i, 1) temp Then temp = arr(i)Next iNext j'最后报告最大值及执行时间MsgBox "最大值为" & temp & Chr(10) & "执行时间:" & Format(Timer - tim, "0.00") & "秒"End Sub该过程采用纯 VBA 法,利用循环获取最大值,执行 100000 次后其时间大概在 1秒钟左右。Sub 获取数组最大值 2() '循环执行 100000 次 工作表函数法Dim arr(), temp As Byte, i, j, tim As Longtim = Timer '获取当前时间For j = 1 To 100000 '循环 100000 次,arr = Array(1, 7, 8, 6, 9, 3, 5, 7, 6, 8, 9, 4, 1, 2, 4)temp = WorksheetFunction.Max(arr) '利用工作表函数一次性获取最大值Next j'最后报告最大值及执行时间MsgBox "最大值为" & temp & Chr(10) & "执行时间:" & Format(Timer - tim, "0.00") & "秒"End Sub该过程采用工作表函数进行计算,代码精简一些,不使用循环即计算出数组的最大值。然后它的执行时间大概在 8 秒钟左右。与前者在效率一存在较大的差异。本例文件参见光盘:..\ 第九章\获取数组最大值.xlsm9.2.13利用对象循环替代单元格循环如果对当前表中与单元格关联的对象进入某项操作时,如果循环单元格方式可以Excel VBA 程序开发自学通2020-2-26第 184页 /共 508页完成,循环其它对象也可以完成,那么尽量对其它对象进行循环,从而减少循环次数。例如当前表中有多个图片,统计有多少单元格被图片覆盖,那么利用单元格循环,逐个判断它是否被覆盖的方式可以达成需求。而利用图片循环,逐个统计图片的覆盖区域的方式也可以完成。然而工作表中单元格数量通常远远多于图片数量,那么单元格循环的次数自然也超过图片循环的次数。再如统计当前表所有带有批注的单元格,如果逐个单元格判断是否具有批注,最后再合计是一种可行的方法。但更好的是循环批注,直接找到每个批注,再利用 Parent获取该批注的父对象单元格即可。假设工作表中已区域有 10000 个单元格,其中只有两个单元格具有批注,那么循环单元格的方式需要判断 10000 次,而循环批注的方式仅仅需要循环两次罢了。两种循环方式的代码如下:Sub 单元格循环() '获取具有批注的单元格地址Dim rng As Range, Address As String, bl As BooleanOn Error Resume NextFor Each rng In ActiveSheet.UsedRangebl = rng.Comment.VisibleIF Err = 0 Then Address = Address & rng.Address(0, 0) & Chr(10)Err.Clear:Next rngMsgBox AddressEnd SubSub 批注循环() '获取具有批注的单元格地址Dim com As Comment, Address As StringFor Each com In ActiveSheet.CommentsAddress = Address & com.Parent.Address(0, 0) & Chr(10)NextMsgBox AddressEnd Sub编程需要涉及很多对象,不同工作人员所涉及的对象各自不同。根据经验,不同行业的程序员会有不同的提速方法。本章仅对提速方面略作介绍,限于篇幅及个人经验所限,无法为读者罗列出所有代码优化技巧。随着编程时间的增长,读者也会将有自己的心得,编写出效率更高的程序。第十章常用语法剖析学习 VBA,最重要的是语法,其它的对象、属性等等不需要花费太多时间去记忆,可以借助 VBA 内部的“自动成员列表”来完成输入。VBA 中涉及的语法很多,其中常见有输入输出语句、循环语句、条件语句、With语句及防错语句。如何驾驭好这些语句对于程序员来说一个非常重要的课题。本章要点:Excel VBA 程序开发自学通2020-2-26第 185页 /共 508页输入、输出语句条件判断语句循环语句With 语句错误处理语句10.1 输入、输出语句以前面章节中,出现了大量的输入、输出语句,本节对 VBA 中的输入、输出语句进行详细地讲解。10.1.1Msgbox 函数的功能及作用VBA 最常见的信息输出函数是 Msgbox 函数,在任何 VBA 的书籍中,它所出现的频率都是最高的。在英文中,Msg 表示 Message,即消息,而 Msgbox 则表示信息框。顾名思义,Msgbox 是用于在屏幕中显示一些信息的对话框,告诉用户需要做什么,或者程序运算的结果,或者某操作的步骤说明等等等等,它的作用极其广泛。如果一定要对 Msgbox 的用途做一归纳,那么笔者认为可以站在开发者立场,对程序开发中它所具备的功能做一分类。1.返回运算结果告诉用户 VBA 的运算结果通常有三种模式:存入工作表、打印到文件和利用消息框返回结果。通常对于临时性的、不需要储存的信息可以利用对话框来展示,它的特点是关闭窗口后就完全消失,不占用任何内存空间。2.询问执行方式对于某些有多种执行选项的操作,例如隔行着色工作表,它可以对奇数行着色,也可以对偶数行着色,为了体现程序的通用性和灵活性时,往往弹出一个提示框让用户选择执行方式,这是最佳的程序开发思路。例如图 10.1,用户单击不同按钮时,VBA 会进行不同的执行方式。图 10.1 利用 Msgbox 询间执行方式3.提示执行步骤在设计 VBA 程序时,如果后续需执行的操作较复杂,应该通过一个消息框来提Excel VBA 程序开发自学通2020-2-26第 186页 /共 508页示用户。包括该程序大概有多少步骤,各步骤中需要注意哪些问题,或者在什么情况下需要跳过什么步骤等,从而减少程序出错的机率。4.告知错误原因终端用户在执行 VBA 程序时,总会有或多或少的错误产生。有时是程序员粗心写错代码造成,有时是代码的兼容性造成,有时是代码完全正确但用户的数据不规范造成。而 VBA 很多时候返回的错误提示让人摸不着头脑,程序员有必要预先设置更有意义的错误提示,告诉用户产生此错误的可能情况。例如,当工作表保护时执行以下语句,一定会产生图 10.2 所示的错误提示:[a1] = 1从图片和代码进行分析,错误提示与代码实际出错的原因风马牛不相及,这完全不利于终端用户了解程序出错的原因。为了避免这种差错,开发程者需要通过一个信息框来展示更有意义的错误提示。例如图 10.3,用户可以立刻明白错误原因,从而修正执行方式。图 10.2 工作表保护时执行单元格写入所产生的提示图 10.3 定义更有意义的提示框5.展示当前状态类似于 DOS 程序中“请按任意键继续……”一样,VBA 也可以在程序执行时显示当前的状态,然后当用户单击回车后再继续执行。6.设计程序帮助Excel 自身的所有功能都有相应的说明信息,利用 VBA 开发程序时也需要设计一帮助系统,不管这个系统大小如何,一定要有相应的说明。通常包括三方面的内容:(1)程序功能说明(2)程序版本及各版本修改内容阐述(3)本程序适用范围,即兼容哪些 Office 版本,或者适用哪个行业当然,利用 Msgbox 制作帮助界面受字符长度限制,只能提供一些简单且简短的信息。10.1.2Msgbox 函数的语法Msgbox 用于在对话框中显示消息,等待用户单击按钮,并返回一个 Integer 类型的值。当用户单击其中按钮后将返回信息给 VBA,程序员可以根据这个返回值来决定后续的操作。Excel VBA 程序开发自学通2020-2-26第 187页 /共 508页1.Msgbox 的参数Msgbox 的具体语法如下:MsgBox(prompt[, buttons] [, title] [, helpfile, context])其中 Prompt 参数是必选参数,其作参数是可选数。各参数功能详解如下表 10-1所示:表 10-1部分PromptButtonsTitleHelpfileContextMsgbox参数详解描述字符串表达式,作为显示在对话框中的消息。prompt 的最大长度大约为 1024 个字符,由所用字符的宽度决定指定显示按钮的数目及形式、使用的图标样式、缺省按钮是什么以及消息框的强制回应等在对话框标题栏中显示的字符串表达式。如果省略 title,则将应用程序名放在标题栏中字符串表达式,识别用来向对话框提供上下文相关帮助的帮助文件数值表达式,由帮助文件的作者指定给适当的帮助主题的帮助上下文编号其中最重要的是前两个参数,第一个参数决定显示的信息,不超 1024 个字符的文本;第二个参数决定图标及按钮。2.Msgbox 的按钮与图标Msgbox 的第二参数可以有多种组合,实现不同的按钮与图标样式。在表 10-2 罗列出了 VBA 中 Msgbox 提示的常数列表。表 10-2常数vbOKOnlyVbOKCancelVbAbortRetryIgnoreVbYesNoCancelVbYesNoVbRetryCancelVbCriticalVbQuestionVbExclamationVbInformationvbDefaultButton1vbDefaultButton2vbDefaultButton3vbDefaultButton4vbApplicationModal值0123451632486402565127680vbSystemModal4096vbMsgBoxHelpButtonVbMsgBoxSetForegroundvbMsgBoxRightvbMsgBoxRtlReading16384655365242881048576Msgbox图标与按钮常数详解描述只显示 OK 按钮显示 OK 及 Cancel 按钮显示 Abort、Retry 及 Ignore 按钮显示 Yes、No 及 Cancel 按钮显示 Yes 及 No 按钮显示 Retry 及 Cancel 按钮显示 Critical Message 图标显示 Warning Query 图标显示 Warning Message 图标显示 Information Message 图标第一个按钮是缺省值第二个按钮是缺省值第三个按钮是缺省值第四个按钮是缺省值应用程序强制返回;应用程序一直被挂起,直到用户对消息框作出响应才继续工作系统强制返回;全部应用程序都被挂起,直到用户对消息框作出响应才继续工作将Help按钮添加到消息框指定消息框窗口作为前景窗口文本为右对齐指定文本应为在希伯来和阿拉伯语系统中的从右Excel VBA 程序开发自学通2020-2-26第 188页 /共 508页到左显示以上表 10-2 中,实际包括四组信息,其中第一组值 (0–5) 描述了对话框中显示的按钮的类型与数目;第二组值(16, 32, 48, 64) 描述了图标的样式;第三组值 (0, 256,512) 说明哪一个按钮是默认值;而第四组值 (0, 4096) 则决定消息框的强制返回性。Msgbox 的第二参数可以从四组中选择值相加来任意设置信息框的显示样式。例如需要显示一个两行信息且带一个 OK 按钮的对话框,那么可以使用以下代码:MsgBox "第一行" & Chr(10) & "第二行", vbOKOnly执行后效果如图 10.4 所示。如果需要显示一个确定按钮与一个取消按钮,且标题显示为“提示”,那么可用以下代码:MsgBox "现在开始执行?", vbOKCancel, "提示"执行后效果见图 10.5 所示:图 10.4两行信息且带一个 OK 按钮图 10.5一个确定按钮与一个取消按钮如果同样是显示图 10.5 的效果,如果需要默认选中第二个,那么代码可以修改如下:MsgBox "现在开始执行?", vbOKCancel + vbDefaultButton2, "提示"Msgbox 的第二参数使用了“vbOKCancel + vbDefaultButton2”,即表示默认值为第二个按钮,当用户直接单击回车键时是按下“取消”而非“确定”。效果图 10.6 所示:如果在需要显示“是”和“否”按钮,再加入一个问号图标,且使用数字来表示按钮样式,那么代码如下:MsgBox "继续执行下步?", 292, "继续"代码中 292 的计算方式是:4 + 32 + 256,其中 4 表示显示“是”和“否”的按钮,32 代表显示问号图标,而 256 则表示默认默认按钮为第二个。执行后效果见图 10.7所示:图 10.6一个确定按钮与一个取消按钮默认第二个图 10.7两个按钮加问号图标当然,根据表 10-2 所示的常数值还可以有很多种组合,读者可以自行测试。3.Msgbox 的返回值Msgbox 主要作用是显示一些信息给终端用户,然而对于程序开发者来说,更重要的一个功能是它具有返回值,且可以根据返回值决定下一步操作。Excel VBA 程序开发自学通2020-2-26第 189页 /共 508页Msgbox 的返回值有 7 种,见表 10-3 所示:表 10-3Msgbox的返回值常数vbOKvbCancelvbAbortvbRetryvbIgnorevbYesvbNo值1234567描述OKCancelAbortRetryIgnoreYesNoMsgbox 的返回值对于开发者来说比较有用。例如用户单击“是”按钮时,执行后续的操作,如果单击“否”则直接退出程序。那么可以参考以下代码:Sub 工作表改名()'声明变量,用代获取 Msgbox 的返回值Dim msg As VbMsgBoxResult'获取 Msgbox 返回值msg = MsgBox("将前表改名为今日期?", 292, "修改日期")'如果用户单击是IF msg = vbYes Then'执行改名ActiveSheet.Name = DateElse'否则退出程Exit SubEnd IF'其它更多代码.............End Sub执行以上代码时,将弹出图 10.8 所示对信息框,如果用户直接单击回键,那么程序立即退出,不做任何回应;如果用户单击“是”按钮,那么立即以当前日期命名工作表,见图 10.9 所示。图 10.8询问执行方式图 10.9选择“是”按钮则工作表改名再举一个实例,仍然对工作表以当前日期重命名,但如果遇到有重名工作表时弹出包括“重试”、“忽略”和“终止”的对话框。代码如下:Sub 工作表改名()'声明变量,用代获取 Msgbox 的返回值Dim msg As VbMsgBoxResult'设置一个标签err:On Error Resume Next '防错,当出现错误时执行下一步ActiveSheet.Name = Date '将当前工作表命名IF err.Number > 0 Then '如果存在错误(即已经有工作表的姓称等于当前日期)Excel VBA 程序开发自学通2020-2-26第 190页 /共 508页'获取 Msgbox 的返回值msg = MsgBox("存在同名工作表,是否继续?", 2, "修改日期")'如果用户单击“中断”则退出程序IF msg = vbAbort Then Exit Sub'如果用户单击“忽略”,则将当前表命名为日期,并添加左右括号IF msg = vbIgnore Then ActiveSheet.Name = "(" & Date & ")"'如果用户单击“重试”则清除错误设置,然后返回 Err 标签处继续执行IF msg = vbRetry Then err.Clear: GoTo errEnd IFEnd Sub在以上代码中,Msgbox 的第二参数使用 2,即表示产生 “终止”、 “重试”、“忽略”和三个按钮,而单击三个按钮时分别对应 Abort、Retry 及 Ignore 三个返回值。在过程中,如果命名时已经产生重命错误,那么 Msgbox 对话框才可能出现。如果用户单击对话框第一个按钮,则直接终止程序,利用“Exit Sub”来处理;如果用户单击第三个按钮,那么会忽略错误,将工作表命名为日期加括号; 如果用户单击第二个按钮,那么程序会继续返回首行继续执行程序,它将会循环产生同样的对话框,直到用户单击其它选项。本过程的错误提示框见图 10.10 所示,如果单击忽略,则程序执行结果见图 10.11所示。图 10.10重名时的提示信息图 10.11单击忽略时效果本例文件参见光盘:..\ 第十章\ Msgbox 处理重命错误.xlsm在使用 Msgbox 的返回值时,Msgbox 的各参数必须使用括号,而不需要返回值时,这不是一个表达式,则不需要括号。以下两种方式都是错误的 Msgbox 用法:MsgBox( "你好! ", 64, "提示")Result = MsgBox "你好! ", 64, "提示"10.1.3Msgbox 函数的限制Msgbox 函数用于将信息展示给用户,它的应用极其广泛。然而它有自身的一些限制,做为程序员有必要了解它的所有限制,才能恰当地运用好 Msgbox。总体来说,Msgbox 具有三方面的限制。1.字符数问题Msgbox 有最大高度与宽度限制,而且只有在字符超过它的限制时才可以它体现出来。因为 Msgbox 具有自动缩放功能,当信息少时它会自动缩小信息框以适应字符的宽度,此时无法目视它的可用范围。要测试 Msgbox 可显示的最多字符数可以使用以下方式:Excel VBA 程序开发自学通2020-2-26第 191页 /共 508页(1)在[A1]单元格中存放超 1000 个英文字母,在[a2]单元格中存放超过 1000 个汉字;(2)在模块中录入以下语句:Sub Msgbox 测试()MsgBox [a1]MsgBox [a2]End Sub执行以上代码后,可以发现,英文字符可以显示 1024 个左右,而纯汉字只能显示 511 个。如果英文与汉字共用,那么按一个汉字占用两个英文宽度计算。如果需要显示超过以上限制的字符信息,那么只能分屏显示,当然这不是好办法。通常采用窗体控件或者 WScript 技术来突破。2.控制权问题Msgbox 对话框总是拥有焦点,且拥有绝对的控制权。即只要 Msgbox 对话框不关闭,那么代码都会停止运行,只有关闭对话框后才会交还控制权给程序,继续执行其它语句。明白这一点很重要,如果程序在执行过程中需要显示信息,例如当前执行进行,而且不能影响程序地继续执行,那么 Msgbox 方式并非首选。建议使用状态栏信息或者无模体的窗体。图 10.12 即为利用状态显示进度来替换 Msgbox 的效果。图 10.12 状态栏是示程序执行进度3.时间性问题Msgbox 的对话框显示的对话框,如果用户不手动关闭,它会永远存在。如果用户需要它在指定时间自动关闭,那么 Msgbox 也无法达成需求。通常采用三种方法来实现:用户窗体、WScript 技术和 API,在后面小节中将提进行演示。10.1.4利用 WScript 突破 Msgbox 限制WScrip 是一种脚本语言,它也可以实现输出对话框信息,而且可以突破 Msgbox的一些限制。利用脚本语言显示信息可用 WScript.Shell 语言中的 Popup 方法。它的具体语法为:WshShell.Popup(strText, [natSecondsToWait], [strTitle], [natType]) = intButton其中第一参数是显示的信息,第二参数是显示的时间,表示信息框在多少秒钟的自动关闭,第三参数是标题,第四参数是按钮与图标的状态。本书第 19 章将进行更Excel VBA 程序开发自学通2020-2-26第 192页 /共 508页详细的解说。WScript.Shell 语言中的 Popup 方法相对于 Msgbox 有两个优点: 可显示的字符远远超过 1024 可以自动关闭对话框如果 A1 单元格的超过 2000 个字符,那么可以使用以下语句显示 A1 字符串的文本框:Sub 显示 A1 信息()CreateObject("WScript.Shell").Popup [a1], , "提示", 64End Sub如果需要信息显示 3 秒钟自动关闭,可以用以下代码:Sub 自动关闭()CreateObject("WScript.Shell").Popup "三秒钟关闭", 3, "提示", 64End Sub但经过多次测试,以上语句在 Excel 2003 中工作良好,在 Excel 2007 中却无法自动关闭。所以为了体现通用性和稳定性,可以改用 API 来完成这个难题,让 Excel 2003和 Excel 2007 都可以顺利实现 3 秒钟关闭。API 实现的方式如下:Public Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent AsLong, ByVal uElaspe As Long, ByVal lpTimerFunc As Long) As LongPublic Declare Function KillTimer Lib "user32" ( _ByVal hWnd As Long, ByVal nIDEvent As Long) As LongDim TID As LongConst Sec = 3 '可以在这里修改时间Sub CloseTest(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByValSystime As Long)Application.SendKeys "~", True '发送回符,即关闭窗口的命令KillTimer 0, TIDEnd SubSub 三秒钟自动关闭()TID = SetTimer(0, 0, Sec * 1000, AddressOf CloseTest)MsgBox Sec & " 秒种自动关闭窗口", 65, "提示"End Sub本例文件参见光盘:..\ 第十章\ 3 秒钟自动关闭的信息框.xlsm10.1.5Debug.printDebug.print 也可以实现信息输出,不过通常开发人员使用。它只能在 VBE 中的立即窗口显示信息,而终端用户是不需要进入 VBE 界面的。Print 具体语法为:Object.Print [outputlist]当 Object 为 Debug 时,它可以实现将信息输出到立即窗口,通常是开发者调试代码时使用。例如打开当前工作簿路径下的“VBA 教学.xls”文件,在打开文件前程序员需要查看一下路径是否存在或者查看路径所在磁盘名,那么可以通过“Debug.Print”方式将信息显示在窗口。代码如下:Sub 打开当前工作簿同路径下的文件()Dim Paths As String'声明变量Paths = ActiveWorkbook.Path'获取路径Excel VBA 程序开发自学通2020-2-26第 193页 /共 508页Debug.Print Paths '查看路径Workbooks.Open Filename:=Paths & "VBA 教学.xls"End Sub'打开工作簿执行以上代码后,在立即窗口中将产生路径,见图 10.13 所示。图 10.13 在立窗口显示信息提示:如果工作簿未保存,那么将不存在路径,输入的信息只是一个空文本。10.1.6Inputbox 函数的功能与作用Inputbox 函数是 VBA 中用于数据输入的函数,它可以在一对话框来中显示提示,等待用户输入信息或按下按钮,返回用户输入的 String 类型字符串。Inputbox 通常用于为用户提供录入窗口,然后将返窗口中的录入字符串按代码指定方式导入到相应的窗口或者根据输入值来决定后续的操作。例如图 10.14 中,用户的录入信息决定程序的后续执行方式。图 10.14 以录入值确定计算方式10.1.7Inputbox 函数的语法Inputbox 的具体语法如下:InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])其中第一参数是必选参数,其余参数为可选参数。各参数的详细解释如下:表 10-4 Inputbox的参数详解部分Prompt描述作为对话框消息出现的字符串表达式。prompt 的最大长度大约是 1024 个字符,由所用字符的宽度决定。Excel VBA 程序开发自学通TitleDefaultXposYposHelpfileContext2020-2-26第 194页 /共 508页显示对话框标题栏中的字符串表达式。如果省略 title,则把应用程序名放入标题栏中显示文本框中的字符串表达式,在没有其它输入时作为缺省值数值表达式,成对出现,指定对话框的左边与屏幕左边的水平距离。如果省略 xpos,则对话框会在水平方向居中数值表达式,成对出现,指定对话框的上边与屏幕上边的距离。如果省略 ypos,则对话框被放置在屏幕垂直方向距下边大约三分之一的位置字符串表达式,识别帮助文件,用该文件为对话框提供上下文相关的帮助数值表达式,由帮助文件的作者指定给某个帮助主题的帮助上下文编号其中最重要的参数是前面三个,包括提示信息、标题和默认值。在特殊情况下,第四、五参数也具有其实用价值——强制指定对话框的显示位置,从而防止对话框挡住当前窗口。或许从以下案例中,可以加深对 Inputbox 的认识。1.定制“另存为”对话框设计一个用于文件另存的对话框,固定保存在 C 盘下,用户可以随意定制文件名,默认名称为当前日期。代码如下:Sub 工作簿另存()Dim FileName As String'声明变量'弹出一个录入框,让用户指定文件名,默认值为当前日期FileName = InputBox("请输入工作簿新名称", "另存为", Date)'当前工作簿另存到 C 盘中,文件名为用户指定字符ThisWorkbook.SaveAs "c:\" & FileNameEnd Sub执行以上代码时,将弹出一个“另存为”对话框供用户录入新名称,其默认值为当前日期,见图 10.15 所示:图 10.15 定制的“另存为”对话框2.根据指定月份批量创建工作表用户指定的月份,程序批量创建该月每日日期命名的工作表。代码如下:Sub 新建工作表() '批量建立新表,个数等于本月天数,同时对日期命名,并建立目录Dim i As Byte, months As Byte'声明变量'弹出一个对话框,让用户指定月份,默认显示当前月months = InputBox("请输入月份,程序将建立该月每日日期命名的工作表", "确定月份",Month(Date))'批量生成工作表,其个数等于指定月份的天数减去当前已有工作表个数,即确保工作表数量等于该月天数Sheets.AddAfter:=Sheets(Sheets.Count),Count:=Day(DateSerial(Year(Date),months + 1, 0)) - Sheets.Count'将所有工作表重命名,工作表名对应每日的日期Excel VBA 程序开发自学通2020-2-26第 195页 /共 508页For i = 1 To Sheets.CountSheets(i).Name = months & "月" & i & "日" '对每个工作表命名Next iMsgBox "建立完毕!", 64End Sub在以上代码中,Inputbox 可以弹出一个对话框,让用户指定月份,默认值为当前月份。而当前月份的计算方式是利用 Month 函数从当前日期 Date 中获取。其中计算用户指定的月份有多少天时,鉴于 VBA 自动日期转换的特点——将 0日当做上月最后一天处理,所以程序利用 DateSerial 函数将下月 0 日转换成本月最后一天的日期序列,最后再用 Day 函数提取其天数,表示当月有多少天。图 10.16 是 Inputbox 函数设置的对话框,让用户指定月份;而图 10.17 是批量创建的工作表。图 10.16 指定月份的录入框图 10.17 批量创建工作表后的效果如果在 Inputbox 中需要是更多的提示信息,那么可以使用 Chr(10)来分行。例如本例中 Inputbox 语句可以修改为:months = InputBox("请输入月份,程序将建立该月每日日期命名的工作表" &Chr(10) & "例如输入 4 月,则产生的工人表则为 4 月 1 日、4 月 2 日.......", "确定月份",Month(Date))3.将 A1 日期按指定样式转换为星期A1 存放日期,现需将其转换星期,程序需要让用户决定转换方式,即提供四个可选项。达成以上需求可以使用代码:Sub 将 A1 日期转换为星期()Dim Week As Byte'声明变量'提供输入框,让用户选择转换方式。在输入框中可以预览转换后的结果Week = InputBox("请选择转换样式:" & Chr(10) & "输入 1:" & Format([a1], "DDD") &Excel VBA 程序开发自学通2020-2-26第 196页 /共 508页Chr(10) _& "输入 2:" & Format([a1], "DDDD") & Chr(10) & "输入 3:" & Format([a1], "AAA") &Chr(10) _& "输入 4:" & Format([a1], "AAAA"), "选择转换样式", 1)'根据用户录入的数字对 A1 的日期进行转换[b1] = Format([a1], Choose(Week, "DDD", "DDDD", "AAA", "AAAA"))End Sub该过程中利用 Inputbox 显示一个输入框,在输入框中可以预览转换后的四种日期样式,只用输入 1 到 4 之间的任何数字,程序会对应地转换日期为该格式。输入框外观见图 10.18 所示。图 10.18 提示用户选择转换样式在该过程中,使用了 Choose 函数,它可以根据第一参数的值从后面的参数中选择对应的值做为 Format 的参数。程序没有使用防错功能,如果输入的值小于 1 或者大于 4 将产生错误。本例文件参见光盘:..\ 第十章\将 A1 日期转换成星期.xlsm10.1.8借用 Inputbox 函数生成月历本节再例举一个 Inputbox 之高级应用,通过用户指定月份生成月历,月历中包括该月每一天及对应的星期。本例可以做为一个完美的工具供用户使用。工具涉及知识如下:(1)数据类型转换(2)错误设置(3)日期的转化(4)区域合并(5)VBA 录入数组公式(6)文本替换(7)为单元格设置边框(8)将公式转换成值具体代码如下:Sub 生成月历()On Error GoTo endd'防错:如果写入失败则动行 Endd 标签的语句Dim Months As Byte'提供一个让用户指定月份的对话框,对话框显示屏幕左上角,其上边距和左边距均为 10'inputbox 反回值是 String 型,利用 CByte 转换成 Byte 型Months = CByte(InputBox("请指定月份,程序将生成该月的月历", "月份", Month(Date),10, 10))IF Months 12 Then MsgBox "只能在 1-12 之间,请重新输入。", 64, "提示": Exit SubExcel VBA 程序开发自学通2020-2-26第 197页 /共 508页Application.ScreenUpdating = False'关闭屏幕更新,加快速度With ActiveCell'在当前单元格显示当前日期.Value = Format(DateSerial(Year(Date), Months, 1), "yyyy 年 m 月 d 日")'对首行合并居中.Resize(1, 7).Merge.HorizontalAlignment = xlCenter' 设置标题行数据并设置为居中显示产,添加颜色With .Offset(1, 0).Resize(1, 7).Formula = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")'标题.HorizontalAlignment = xlCenter '居中显示.Interior.ColorIndex = 15'标示背景色.Font.Bold = True '加粗显示End WithWith .Offset(2, 0).Resize(6, 7) '设置公式区域'建立数组公式.FormulaArray="=text(IF(MONTH(DATE(y,m,1))MONTH(DATE(y,m,1)-(WEEKDAY(DATE(y,m,1))-1)+{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1),"""",DATE(y,m,1)-(WEEKDAY(DATE(y,m,1))-1)+{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1),""d"")"'将公式中的辅助字符替换.Replace What:=",m,", Replacement:=",MONTH(" & ActiveCell.Address(0, 0)& "),".Replace What:="y,", Replacement:="YEAR(" & ActiveCell.Address(0, 0) &"),".HorizontalAlignment = xlCenter '居中.Value = .Value '将公式转换成值.EntireColumn.AutoFit'自动调整列宽End With.Resize(8, 7).Borders().LineStyle = xlContinuous'添加边框,中间部分'再添加外框,外框显示为加粗.Resize(8, 7).BorderAround ColorIndex:=1, Weight:=xlThickEnd WithApplication.ScreenUpdating = TrueExit Subendd:MsgBox "您输入的月份包括文本" & Chr(10) & "或者当前区域无法写入", 65End Sub录入代码后返回工作表,通过以下步骤测试代码的可行性:(1)选择单元格 A1,然后按下快捷键【Alt+F8】打开“宏”对话框;(2)如果工作簿中仅有一个宏,那么 Excel 会自动选定“生成月历”,否则手工选择宏名“生成月历”并单击“执行”按钮;(3)此时程序将弹出图 10.19 所示对话框,提示用户输入日期,其默认值当前月的月份;(4)输入字母“A”或者输入数字 13,那么程序会弹出图 10.20 所示对话框。因为月份只能是 1 到 12 之间的数字;Excel VBA 程序开发自学通2020-2-26图 10.19 提示录入月份第 198页 /共 508页图 10.20 错误提示(5)如果直接单击“确定“按钮,那么当前工作表中 B1 开始的 7 列 8 行将产生图 10.21 所示月历:图 10.21 月历在产生月历前,需要确保当前单元格开始右下方 7 列 8 行是否有数据,如果该区域非空,则月历会覆盖该区域中的数据;同时还要确保该区域中不存在合并单元格,因月历无法在合并单元格中录入数组公式。如果读者认为以上限制太多,也可以变通地实现,突破这两个限制。方法是在当前单元格右方插入新七个空白列。插入的代码如下:ActiveCell.Resize(1, 7).EntireColumn.Insert Shift:=xlToRight其中 EntireColumn 代表整列,如果仅仅对 7 个单元格进行插入,那么也只能插入7 个单元格;对 7 列进行插入,则会相应地插入 7 列。不过,在本过程中防错仍然非常重要,尽管可以插入 7 列的来解决前面所提到的问题。因为开发者很难完全预料用户在用在什么情况下使用此命令。例如还有一些较极端的现状,例如用户选择一个图形对象再执行程序,或者当前活动单元格位于工作表最后一行等等。本例文件参见光盘:..\ 第十章\生成月历.xlsm10.1.10Inputbox 函数的限制前面展示了 Inputbox 函数的功能及其在实际应用的神奇,但也不代表它没有缺陷。它不仅有缺陷,而且较多,要更好地发挥程序的功能、更好地运用好字符输入框,就有必要了解它到底有哪些限制。整体来讲,Inputbox 函数主要有以下限制,制约着它无法更好的在 VBA 中展现其功能:Excel VBA 程序开发自学通2020-2-26第 199页 /共 508页1.不能检验用户录入字符的数据类型通常,在 VBA 中期望终端用户录入什么类型的数据,但有时终端用户基于测试或者其它目的,会故意录入错误的数据,此时程序可能会弹出与实际错误不相符的提示。而做为开发者,有必要防范此事故发生。例如图 10.15 中,要求用户输入 1 到 12 的数字,但用户如果故意输入“5 月”或者“五”,那么程序会产生错误,且对于此类错误,Inputbox 并没有内置任何防范措施。2.不能让产生单元格引用如果用户需要输入单元格地址,在 Inputbox 对话框中,用户只能手工录入地址。这显然效率不高且容易产生错误。例如在 Excel 2007 使用兼容模式下却手工输入了“ZZ100”之类的错误。而更理想的方式让用户直接选择区域,程序自动将选区地址返回给对话框。这种问题只能留给 Application 对象的 Inputbox 方法来处理。3.字符长度限制对话框中的字符串和用户录入的字符(即第一和第三参数)都限制定 1024 左右,如果是纯汉字则为 511 个。如果用户的需求超过以上限制,应该考虑使用窗体来完现。10.1.11利用 Application.Inputbox 方法替代Inputbox 函数VBA 中 有 一 个 比 Inputbox 函 数 功 能 类 似 , 却 强 大 很 多 的 语 句——Application.Inputbox 方法。Application.Inputbox 方法在功能上与 Inputbox 函数基本一致,但却提供了数据类型检测和直接产生区域引用的功能,大大方便用户的使用。在工作中尽量使用 Application.Inputbox 方法替代 Inputbox 函数。相对于 Inputbox函数,Application.Inputbox 方法在用法上基本一致,只是多了一个指定数据类型的参数,却在使用中提供了很多的便利。使用中一定要要注意,不对用户录入信息进行验证的是 VBA 中的 Inputbox 函数,带验证功能的是 Application.Iputbox 方法。10.1.12Application.Inputbox 语法详解Application.Inputbox 方法的基本语法如下:Application.InputBox(Prompt, Title, Default, Left, Top, HelpFile, HelpContextID,Type)表 10-5 中包括了 Application.Inputbox 方法的各参数详解。表 10-5Application.Inputbox方法的各参数详解Excel VBA 程序开发自学通名称必选/可选Prompt必选Title可选Default可选LeftTop可选可选HelpFile可选HelpContextIDType可选可选2020-2-26第 200页 /共 508页描述要在对话框中显示的消息。可为字符串、数字、日期、或布尔值(在显示之前, Excel自动将其值强制转换为String)输入框的标题。如果省略该参数,默认标题将为“Input”指定一个初始值,该值在对话框最初显示时出现在文本框中。如果省略该参数,文本框将为空。该值可以是 Range 对象指定对话框相对于屏幕左上角的 X 坐标(以磅为单位)指定对话框相对于屏幕左上角的 Y 坐标(以磅为单位)此输入框使用的帮助文件名。如果存在 HelpFile和HelpContextID参数,对话框中将出现一个帮助按钮HelpFile 中帮助主题的上下文 ID 号指定返回的数据类型。如果省略该参数,对话框将返回文本Application.Inputbox 方法有 8 个参数,其中最重要的是前三个和最后一个。Type参数可以指定种或者多种数据类型,而 Application.Inputbox 方法则会根据该类型对用户的录入信息进行检查,如果不符合指定类型则会阻止程序执行。现对第八个参数在工作中的应用展示三个案例。1.强制用户录入数值以图 10.19 中提示用户录入月份为例,强制用户录入 1 到 12 月的数值,否则程序拒绝执行。Application.Inputbox 方法完全可以胜任,代码如下:Months = Application.InputBox("请指定月份,程序将生成该月的月历", "月份",Month(Date), 10, 10, , , 1)将以上代码替换“生成月历”中对应的代码可即,其中最后一个参数 1 表示只能录入数值。当用户执行代码时,如果在对话框中录入非数值“5 月”,那么程序将提示“无效的数字”,见图 10.22 所示:图 10.22录入非数值时的提示2.对任意选区进行行列合计以本书第七章中 7.3.10 案例二为例,该代码的缺陷在于必须运行程序前选择正确的区域,否则将产生错误结果,而这显得灵活度不足。借用 Application.Inputbox 方法可以弥补这个缺陷。修改后的代码如下:Sub 行列自动合计()Dim rng As Range, address As String'声明一个对象变量'如果当前选择的对象是单元格则将单元格地址赋与变量,否则将空文本赋与变量IF TypeName(Selection) = "Range" Then address = Selection.address Else address =""'弹出一个对话框,让用户选择区域,默认显示变量 address 的值。然后将该用户选择区Excel VBA 程序开发自学通2020-2-26第 201页 /共 508页域赋与变量 rngSet rng = Application.InputBox("请选择待合计的区域", "合计区域", address, , , , , 8)IF rng Is Nothing Then Exit Sub'先汇总各行的值For i = 1 To rng.Rows.Count '从 1 到总行数'利用 Offset 取得汇总数据的放置位置,即选区第一个单元格向右偏移选区的列数'合计区域也用 Offset 逐行偏量来获取,Resize 的作用是重置为 1 行,否则会汇总其它行的数据rng(1).Offset(i - 1, rng.Columns.Count) = WorksheetFunction.Sum(rng.Offset(i 1).Resize(1))Next'再汇总各列的值For i = 1 To rng.Columns.Count + 1'从 1 到总列数加 1,因为需要对行的汇总数再进行汇总rng(1).Offset(rng.Rows.Count, i - 1) = WorksheetFunction.Sum(rng.Offset(, i 1).Resize(, 1))NextEnd Sub在本过程中,相对于 7.3.10 的代码加入了选区对象检测及 Application.InputBox语句。选区检测是为了获取选择对象的地址,并将该地址做为输入框中的默认值,它给用户提供一些便利。如果用户当前选区正是待计算区域,那么可以直接执行运算;如果当前区域非待计算区域时,则可手动选择目标区域。这相对原有代码更具人性化。Application.Inputbox 方法的第八参数使用 8,表示返回单元格引用,其数据类型为 Range。在执行本过程时,不强制要求用户选择待计算区域,可以在执行程序后再确定区域。图 10.23 即为手工拖动鼠标选择区域时,对话框相应地产生区域地址:图 10.23通过鼠标拖动录入区域地址本例文件参见光盘:..\ 第十章\任意选区行列自动求和.xlsm3.利用 Application.Inputbox 录入公式在单元格中录入公式时,Excel 会对公式进行检查,如果不符合公式的基本语法会阻止用户录入。而 VBA 中的 Inputbox 方法也可以实现同等功能。例如对图 10.24 中的数据进行排名次,如果使用 VBA 的对话框来录入公式,那么代码如下:Sub 设置计算名次的公式()'首先选择待输入公式的单元格[c2].SelectExcel VBA 程序开发自学通2020-2-26第 202页 /共 508页'设置 C2 的公式,第 8 参数必须用 0,否则单元格中显示值而非公式[c2].FormulaLocal = Application.InputBox("请输入计算名次的公式:", "公式", , , , , , 0)'填充公式Range("C2").AutoFillDestination:=Range("C2:C"&Cells(Rows.Count,2).End(xlUp).Row)End Sub该过程中,Application.Inputbox 第八个参数使用 0,表示在 C2 单元格产生公式,如果使用其它值做为参数则只能产生公式的结果,而非公式本身。利用 Application.InputBox 方法录入公式需要注意四点:(1)在弹出对话框前必须先定位于目标单元格,否则公式中引用的单元格或者区域会产生错位,类似于条件格式中的引用;(2)在对话框中录入公式时,可以利用鼠标单击单元格来产生地址,而且可以通过快捷键【F4】使其在相对引用、绝对引用与混合引用三个状态之间切换,和直接在单元格中录入公式的方式一致;(3)在代码中必须对存放公式的单元格使用 FormulaLocal 属性,那么 VBA 就会对录入的公式进行检测,如果录入的字符不符合公式的格式,那么将阻止程序继续执行,从而确保公式的正确性;(4)如果需要在单元格中录入数组公式,则需要使用 FormulaArray 属性。代码如下:[c2].FormulaArray = Application.InputBox(" 请 输 入 计 算 名 次 的 公 式 : ", " 公 式", , , , , , 0)执 行 本 过 程 时 , VBA 会 弹 出 一 个 输 入 公 式 的 对 话 框 , 在 其 中 录 入 公 式“=rank(B2,$B$2:$B$8)”然后单元格 C2 会自动产生公式,且将公式向下填充,直到B 列最后一个非空单元格。图 10.24待排名次的成绩表图 10.25在对话框录入排名次的公式如果在其中录入一个不完整的公式“=rank(B2,$B$2:$B$8”,那么 VBA 会提示用户公式缺少括号,见图 10.26 所示。Excel VBA 程序开发自学通2020-2-26图 10.26第 203页 /共 508页公式缺少括号时弹出提示框如用户在对话框中录入公式时忽略了等号,那么 VBA 将它当做文本字符串,自动添加引号及等号。例如用户录入“rank(B2,$B$2:$B$8)”,那么单元格中则会产生以下公式:="rank(B2,$B$2:$B$8)"Application.Inputbox 方式录入的公式可以产生有看不见的工作表中,这是相对手工录入公式优越性。例如 sheet2 属于隐藏状态,那么以下语句完全不影响正常执行,仍然可以在目标单元格产生正确公式:Sheet2.[c2].FormulaLocal = Application.InputBox("请输入计算名次的公式:", "公式", , , , , , 0)本例文件参见光盘:..\ 第十章\ Inputbox 方法录入公式.xlsm10.2 条件判断语句条件判断语句在 VBA 中也是使用率非常高的语句。用户在 Excel 中录制宏时无法如何都无法产生条件语句,必须通过 VBE 界面手工编写代码,那么了解它的语法就显得犹为重要了。条件语句主要包括以下五种: IIF IF…Then… IF…Then…End IF Select Case…End Select Choose本节将对以上五种条件语句进行详述。10.2.1IIF 函数的语法与应用IIF 函数是 VBA 中类似于工作表 IF 的条件函数,它基于条件返回不同的值。下面展示它的参数及用法。Excel VBA 程序开发自学通2020-2-26第 204页 /共 508页1.IIF 的参数IIF 函数可以根据表达式的值,来返回两部分中的其中一个。它的基本语法为:IIF(expr, truepart, falsepart)IIF 的三个参数均为必选参数,各参数的含义见表 10-6 所示:表 10-6部分exprtruepartfalsepartIIF的参数详解描述用来判断真伪的表达式如果 expr 为 True,则返回本参数的值或表达式如果 expr 为 False,则返回本参数的值或表达式如果需要表达大于等于 60 分时返回“及格”否则返回“不及格”,那么可用以下语句:IIF([a1] >= 60, "及格", "不及格")如果第二、三条件的字符较长,且不同的字符较少,为了缩短代码,也可以改用以下方式:"A1 的成绩" & IIF([a1] >= 60, "", "不") & "及格"即把相同部分置于 IIF 语句之外,用 IIF 的第二、三参数来决定不同的部分。再如 A1 大于 B1 时,则返回 C1 的值,否则返回 C1 值的 50%,那么可用以下语句:IIF([a1] > [b1], [c1], [c1] / 2)也可以改用以下方式,将 C1 置于 IIF 语句之外,代码如下:[C1] / IIF([a1] > [b1], 1, 2)2.And 运算符与 Or 运算符当 IIF 函数使用多条件时,必须借助 And 运算符与 Or 运算符来连接其条件。如果需要同时满足多条件时,可使用 And 运算符。And 运算符的主要作用是对两个表达式进行逻辑连接。表达式如下:result = expression1 And expression2其中 result 与 expression1、expression2 之间的关系见表 10-7 所示:表 10-7如果 expression1为TRUETRUETRUEFALSEFALSEFALSENullNullNullAnd运算符参数与结果之关系且 expression2为TRUEFALSENullTRUEFALSENullTRUEFALSENull如果有三个条件可以采用以下表达式:result = expression1 And expression2 And expression3则 result为TRUEFALSENullFALSEFALSEFALSENullFALSENullExcel VBA 程序开发自学通2020-2-26第 205页 /共 508页如果某行业招聘时要求体重在 50 到 65 公斤之间合格,否则不合格,那么 VBA表达方式如下:Msgbox IIF([a1] > 50 And [a1] 100 Or [a1] < 1, "录入错误", "")IIF 函数也可以嵌套使用,即一句代码中使用多个 IIF,根据两个以上的条件返回对应的值,而且每个条件参数也可以借用 And 或者 Or 运算符来连接。当 And 和 Or 共用一个参数的时候,尽量采用括号来体现优先顺序。例如(expression1 And expression2) Or (expression3 And expression4)3.IIF 应用案例实例一:根据录入的月份计算季度利用录入框让用户录入月份,默认为当前月,并根据月份判断其季度。代码如下:Sub 根据月份判断季度()Dim Months As Byte '声明变量Star: '设置一个标签'弹出对话框让用户录入月份,默认为当前月份Months = Application.InputBox("请输入月份,只能是数字", "月份", Month(Date), , , , , 1)'如果录入的数值小于 1 或者大于 12 则返回标签 Star 处继续执行IF Months 12 Then MsgBox "只能在 1 到 12 之间": GoTo Star'四个 IIF 嵌套运用,其中每个 IIF 的第一参数使用双条件,在双条件时需要用 And 连接MsgBox IIF(Months > 1 And Months 3 And Months 6 _And Months 9 And Months 12 And Cells(i, "C") > 0.15) Or ((Cells(i, "B") > 8 _And Cells(i, "C") > 0.12) And Cells(i, "D") = "是"), 2000, 1000)Next iEnd Sub该过程中 And 运算符与 Or 运算符合用,显得较复杂,但可以顺利地完成需求。读者需要将代码与题目要求进行详细的对比,理解代码的设计思路。图 10.29奖金计算表本例文件参见光盘:..\ 第十章\多条件计算奖金 1.xlsm实例三:利用 IIF 计算当前 Office 版本目前用户最常用的三个 OFFICE 版本分别是 Office 2002、2003 和 2007,以下程序可以返回 OFFICE 的版本。代码如下:Sub Office 版本()MsgBox "Excel 200" & IIF(Application.Version = 10, "2", IIF(Application.Version = 11, 3,IIF(Application.Version = 12, 7, "")))End SubIIF 函数的应用极广,用户可以自己多多测试。提示:条件语句永远无法在录制宏中产生,读者只能编过手工编写,从实践中摸索条件语句的Excel VBA 程序开发自学通2020-2-26第 207页 /共 508页使用技巧10.2.2IIF 函数的限制IIF 是一个函数,与工作表函数 IF 极其相似。但是在使用上,相对于 IF 函数却不够灵活,主要体现在以下方面。 第三参数是否必选参数IIF 的第三参数是必须参数,而 IF 函数的第三参数是可选参数。例如工作表中可以使用以下公式,仅仅声明满足条件时的反回值:=IF(A1>=60,"及格")但是使用 IIF 时必须使用第三参数,否则将产生编译错误。 是否检验第三参数当第一参数——条件为 True 时,IF 函数可以忽略第三参数;而 IIF 函数则会同时样检验第三参数的值,如果第三参数存在错误值则程序会中断。例如以下语句:IIF([a1] > [b1], [b1] / [a1], [a1] / [b1])当单元格 a1 大于 0 且 b1 为 0 时,程序会产生编译错误。因为 IIF 的特点是条件成立仍然检测第三数;如果使用工作表函数 IF,则不会产生任何错误。 错误方式当 IF 函数计算结果为错误值时,它会在单元格中产生对应的值,不影响其它单元格中的公式;而 IIF 的第三参数中假设使用了零做除数,那么整个过程都会中断,并提示错误信息。虽然 IF 较 IIF 更好用,但是在 VBA 中不能调用工作表函数 IF,只能调用 IFError函数。10.2.3IF… Then…语句的语法详解IF…Then…句式的条件语句相比 IIF 函数在功能上强大很多。IF…Then…不是函数,而是根据表达式的值有条件地执行一组语句。它的语法如下:IF condition Then [statements]即如果满足条件则执行指定的语句或者一组语句。如果有多句代码需要利用冒号进行连接。两个能数均为必选参数,缺一不可。其中条件语句必须是数值表达式或字符串表达式,其运算结果为 True 或 False。如果 condition 为 Null 时,则 VBA 将其视为 False。例如:变量 A 大于 60,则变量 B 等于“及格”,其表达式为:IF A > 60 Then B = "及格"如果 A1 单元格字符数超过 3 个,那么将 A1 单元格字符加粗再倾斜,表达式如下:IF Len([a1]) >= 3 Then Range("a1").Font.Bold = True: Range("a1").Font.Italic = TrueIF… Then…语句句式的条件语句与 IIF 函数中的条件一样也可以使用 And 和 Or运算符来连接多个条件。And 与 Or 运算方参见表 10-7 与表 10-8。Excel VBA 程序开发自学通10.2.42020-2-26第 208页 /共 508页IF…then…应用案例现通过几个案例展示 IF…Then…句式的用法,让读者对它有进一步的认识。1.禁止打印“总表”以外的工作表工作簿中有很多工作表,禁止用户打印工作表以外的任何工作表数据,实现步骤如下:(1)按下快捷键【Alt+F11】进入 VBE 界面;(2)如果未显示工程资源管理器,则使用快捷键【Ctrl+G】显示工程资源管理器。然后双击 ThisWorkbook 进入工作簿事件代码窗口;(3)在窗口中录入以下事件过程代码:'声明工作簿打印事件Private Sub Workbook_BeforePrint(Cancel As Boolean)'如果工作表名不等于“总表”就禁止打印IF ActiveSheet.Name "总表" Then MsgBox "禁止打印": Cancel = TrueEnd Sub(4)返回工作表界面,选择任意工作表并单击“打印”,将弹出 10.30 所示提示,同时禁止工作表打印。图 10.30 打印总表工外的工作表时的提示信息在该事件过程中,“Cancel”参数用于控制是否可以打印,对当前工作簿中所有工作表都生效,但当前要求是允许打印“总表”,所以必须借助 IF 语句排除“总表”,对当前工作表名不等于“总表”者进行限制。在本例的条件语句中,符合条件时需要执行两句代码,包括提示及禁止打印,那么两句代码之间必须使用冒号分隔,且必须写在同一行中。本例文件参见光盘:..\ 第十章\禁止打印总表以外的工作表.xlsm2.仅仅允许 8 到 18 点可以打开当前工作簿某工作簿仅仅允许在早 8:00 至下午 18:00 之间可以开启,其它时间开启后则自动关闭。实同步骤如下:(1)按下快捷键【Alt+F11】进入 VBE 界面;(2)如果未显示工程资源管理器,则使用快捷键【Ctrl+G】显示工程资源管理器。然后双击 ThisWorkbook 进入工作簿事件代码窗口;(3)在窗口中输入以下工作簿打开事件代码:'声明工作簿开启事件Private Sub Workbook_Open()'如果现在的小时数大于等于 8,而且小于等于 18 则退出程序IF Hour(Now) >= 8 Or Hour(Now) <= 18 Then Exit Sub'退出 Excel 程序Application.QuitExcel VBA 程序开发自学通2020-2-26第 209页 /共 508页End Sub(4)保存工作簿后再重新开启,如果当前时间小于 8 点钟或者大于 18 点钟,那么工作簿会自动关闭。在该过程中,IF 条件语句中有两个条件,只要满足条件之一即退出程序,所以借用 Or 运算符来连接条件。也可以改用 And 运算符,那么重编代码如下:'声明工作簿开启事件Private Sub Workbook_Open()'如果现在的小时数大于等于 8,而且小于等于 18 则退出 Excel 程序IF Hour(Now) 18 Then Application.QuitEnd Sub在本过程中,与前一个过程可以实现完全相同的功能,只不过对于条件的处理方式不同。本例文件参见光盘:..\ 第十章\允许 8 到 18 点开启工作簿.xlsm3.如果 A1 是数字,则当前工作簿保存为 A1 的值将当前工作簿另存,且文件以 A1 的值命名,但 A1 非数字时例外。实现步骤如下:(1)按下快捷键【Alt+F11】进入 VBE 界面;(2)单击菜单【插入】\【模块】,在模块代码窗口中输入以下代码:Sub 工作簿另存()'如果 A1 是数字,而且非空,那么工作簿保存为 A1 的值IF VBA.IsNumeric([a1]) And Len([a1]) > 0 Then ThisWorkbook.SaveAs "C:\" & [a1],FileFormat:=xlOpenXMLWorkbookMacroEnabledEnd Sub(3)返回工作表界面,在 A1 单元格输入“5 月 1 日”,然后按下快捷【Alt+F8】打开“宏”对话框,选择“工作簿另存”并执行过程,因为 A1 不是数值而不会有任何反应;(4)A1 单元格清除格式,并输入 2005,再次执行过程,工作簿立即被保存为“2005.xlsm”,且存于 C 盘根目录。在本过程中,除了需要判断 A1 是否是数字外,还非常有必要判断其是否非空。因为假设 A1 单元格是空白,那么 IsNumeric 函数会将其当作 0 参与计算,则条件为True,而事实上 A1 单元格是空时需要终止程序继续执行,所以利用 And 连接两个条件,从而精确地判断 A1 是否符合条件。另一点需要注意的是,工用簿另存时不指定文件类型会以 Xlsx 格式保存,而带有宏的工作簿需要以 Xlsm 格式才能将宏代码保存下来,所以在代码中需要使用“FileFormat”参数指定文件格式。本例文件参见光盘:..\ 第十章\以 A1 的值为文件名另存工作簿.xlsm10.2.5IF…Then…Else…语法与应用IF…Then…语句用于满足条件时执行指定的语句,在实际工作中无法满足更多工作需求。IF…Then…Else…语句也根据表达式的值有条件地执行一组语句,然而相对于 IF…Then…语句它还可以指定在满足条件时的需执行的语句,其应用范围更广。Excel VBA 程序开发自学通2020-2-26第 210页 /共 508页1.语法详解IF…Then…Else…语句有两种用法,包括条件与满足条件时的执行语句在同一行及条件与满足条件时的执行语句在不同行两种方式,它们的语法分别介绍如下:同行时语法:IF condition Then [statements][Else elsestatements]其中满足条件时执的语句 statements 和不满足条件时执行的语句 elsestatements 都是可选项,不需要“End if”结束。不同行时语法:IF Condition Then[statements][Else[elsestatements]]End IF条件与执行语句不同行时必须以“End IF”结束条件语句,而“Else”则是可选项。从语法上看,条件及执行语句同行与不同行没大多分别,仅仅写法不同。然而实际工作中,不同行的条件语句会占有一些优势,特别是条件有多个或者执行的语句有多句时。2.实例:多条件计算奖金例如据图 10.31 所示的数据计算提金额,条件为业绩大于 12 万且利润比例大于15%时提成金额为 2000 元,否则为 1000 元,两种解法如下:图 10.31 业绩表Sub 计算奖金 1() '同行Dim i As Byte'从 2 开始至最后一行个非空行结束For i = 2 To WorksheetFunction.CountA([B:B])'根据要求设置条件:如果满足条件,提成 2000,否则提成 1000IF (Cells(i, "B") > 12 And Cells(i, "C") > 0.15) Then Cells(i, "D") = 2000 Else Cells(i,"D") = 1000Next iEnd SubSub 计算奖金 2() '不同行Dim i As ByteExcel VBA 程序开发自学通2020-2-26第 211页 /共 508页'从 2 开始至最后一行个非空行结束For i = 2 To WorksheetFunction.CountA([B:B])'如果满足条件,那么IF (Cells(i, "B") > 12 And Cells(i, "C") > 0.15) ThenCells(i, "D") = 2000 '奖金为 200Else'否则Cells(i, "D") = 1000 '否则奖金为 1000End IF '结束条件语句Next iEnd Sub两个过程的执行结果完全一致,但在代码阅读和理解上前一个过程更方便,也显得更简洁。然而在执行语句较多时,却完全相了,多行句式会有占有优势。本例文件参见光盘:..\ 第十章\多条件计算奖金 2.xlsm再如,单元格 A1 的值大于 60 时,将 A1 的字体加粗倾斜,并添加红色背景。也有两种解法,见图 10.32 所示:图 10.32 两种条件语句写法的比较从图 10.32 进行比较,可以看到当有多行语句时,将 IF…Then…Else…条件语句写为多行会显示更有条理性和层次感。10.2.6条件语句的嵌套应用条件语句用工作表函数 IF 一样,可以多层嵌套,从而满足多条件的需求。1.语法详解条件语句的嵌套也分两种方式,现分别进行介绍如下。 方式一IF Condition Then[statements][ElseIF condition-n Then[elseifstatements] ...[Else[elsestatements]]End IF语法列表中省略号代表可以继续添更多的条件和对应的执行语句。Excel VBA 程序开发自学通2020-2-26第 212页 /共 508页例如:根据图 10.33 所示的成绩对姓名所在单元格进行着色,如果成绩小于 60则填充灰色背景,如果在 60(含)到 100 之间则填充淡蓝色背景,如果 100 分则填充红色背景。完成这个需求可以利用条件语句嵌套完成,代码如下:Sub 根据成绩设置背景色()'遍历所有成绩For i = 2 To Cells(Rows.Count, 2).End(xlUp).Row'如果成绩小于 60 第一层 IFIF Cells(i, "B") < 60 Then'单元格颜色地址设置为 15Cells(i, "A").Interior.ColorIndex = 15'如果成绩小于 100(相对于 60 到 100 但不包括 100),第二层 IFElseIF Cells(i, "B") < 100 Then'单元格颜色地址设置为 17Cells(i, "A").Interior.ColorIndex = 17Else '否则'单元格颜色地址设置为 13 ,代表满分 100Cells(i, "A").Interior.ColorIndex = 3End IFNext iEnd Sub以上过程中 IF 语句设置条件为“<60”,然后将其背景色设置灰色;而 ElseIF 语句则是在前一个 IF 的基础上进行的判断,所以它的条件虽然是“5下面的实例可以展示参数中 expressionlist-n 部分的多种表达形式。2.实例:多条件时间判断根据当前的小时判断是上午、中午,还是下午、晚上、午夜。要求中条件比较多,使 IF…Then…需要多层嵌套,而 Select Case 语句会更简单。代码如下:Excel VBA 程序开发自学通2020-2-26第 216页 /共 508页Sub 时间()Dim Tim As Byte, msg As StringTim = Hour(Now)Select Case TimCase 1 To 11msg = "上午"Case 12msg = "中午"Case 13 To 16msg = "下午"Case 17 To 20msg = "晚上"Case 23, 24msg = "午夜"End SelectMsgBox "现在是:" & msgEnd Sub以上代码中,“Case 1 To 11”表示当前时间在 1 点到 11 点,用于限定一个范围;“Case 12”是表示当前时间为 12 点时返回真,可用于限制一个具体的值,或者多个值。如果需要罗列多个具体的值,那么需要在每个值之间使用逗号分隔,例如“Case23, 24”。本例中不存在例外的情况,所以忽略“Case Else”语句。3.实例:根据成绩返回评语如果成绩小于 60 则返回“不及格”,60 到 80 之间则返回“良”,80 到 99 则返回“优”,100 分则返回“满分”,如果成绩大于 100 则是输入了错误值。利用一个 Function过程来处理,其代码如下:Function 成绩(rng As Range)Select Case rngCase Is 100成绩 = "输入错误"Case Is < 60成绩 = "不及格"Case 60成绩 = "及格"Case 60 To 80成绩 = "良"Case 81 To 99成绩 = "优"Case Else成绩 = "满分"End SelectEnd Function以上代码中,Is 关键词用于指定一种比较方式,后接 Is 和 Like 以外的比较运算符。本例中需要限制小于 0 和大于 100 两个条件,那么 Is 关键词可以同行中使用两次,中间用逗号分隔,表示罗列两个条件。“Case Is < 60”语句在第一个条件“Case Is 100”基础上再进行比较,那么成绩“-10”将不符合“<60”这个条件,因为 Select Case 在处理多条件时,总是按比上到下的顺序处理。如果改变本例条的条件顺序,可能产生不同的运算结果。Excel VBA 程序开发自学通2020-2-26第 217页 /共 508页“Case Else”语句表示如果不符合前面的所有条件,则执行它后面的语句,本例中表示成绩为 100 时返回“满分”。在工作表中使用该自定义函数后,结果见图 10.35 所示。图 10.35 利用 Function 返回成绩评语本例文件参见光盘:..\ 第十章\根据成绩返回评语.xlsm4.实例:以指定格式的今日日期显示工作簿标题Excel 的标题默认显示“Microsoft Excel”,现要求显示为今日日期,且日期需要大小写三种方式供用户选择。具体代码如下:Sub 以今日日期显示标题()Dim str As Stringstar:Select Case Application.InputBox("请指定日期显示方式:" & Chr(10) & "1:数字日期" &Chr(10) _& "2:中文小写" & Chr(10) & "3:中文大写", "日期显示方式", 1, , , , , 1)Case 1str = "yyyy-mm-dd"Case 2str = "[DBNum1] yyyy 年 mm 月 dd 日"Case 3str = "[DBNum2] yyyy 年 mm 月 dd 日"Case ElseMsgBox "录入错误,请重新录入"GoTo starEnd SelectApplication.Caption = WorksheetFunction.Text(Date, str)End Sub本例是“Select Case”对象不确定的实例,它在执行后由用户手动指定对象。在 Select Case 语句中,利用 Case 限制了三个条件,分别为手动录入 1、2 和 3 时的日期显示格式。但用户可能会误输入 1 到 3 范围以外的数值,使程序产生错误,那么利用 Case Else 语句来限制,且返回 InputBox 语句处让用户重新录入,直到数值处于要求的范围内为止,这是最理想的处理方式。本例文件参见光盘:..\ 第十章\以指定格式的今日日期显示工作簿标题.xlsmExcel VBA 程序开发自学通2020-2-26第 218页 /共 508页5.实例:Select Case 嵌套应用计算时间和 IF…Then…一样,Select Case 也可以多层嵌套。本例根据现在的小时数判断白天或者晚上,而如果是白天,再进行细分“上午”、“正午”和“下午”。详细代码如下:Sub 时间()tim = Hour(Now) '获取现在的时间Select Case tim '条件语句开始,第一层Case 8 To 18 '如果是白天Select Case tim '嵌套使用,现二层Case Is < 12 '如果于 12MsgBox "上午"Case 12'如果等于 12MsgBox "正午"Case Else'其它的情况,表示 13 到 18MsgBox "下午"End Select '结束时层条件语句Case Else'其它的情况,外层MsgBox "晚上" '表示 1 到 8 之间及 19 到 24End SelectEnd Sub在 Select Case 语句第一个条件中嵌套了一个 Select Case 条件语句。根据需要,还可以嵌套多层条件语句。10.2.8Select Case 与 IF… Then…Else 之比较Select Case 与 IF… Then…Else 两者都可以实现多条件判断,但在使用中各有优势。IF… Then…Else 的优势在于在条件中可以随时使用 And 与 Or 运算符对多个对象设置条件,而 Select Case 语句只能针对 Select Case 语句中首先指定的唯一条件进行判断。例如在 A1 大于 60 及 B1 小于 60 时执行过程“A”,否则执行过程“B”,IF…Then…Else 语句可以利用以下方式完成:Sub 多对象设置条件() ‘IF…Then…IF [a1] 60 ThenCall AElseCall BEnd IFEnd Sub而 Select Case 语句是无法完成以上条件的,它只能完成 A1 大于 60 或者 A1 小于60 时执行过程“A”,否则执行过程“B”这种需求。代码如下:Sub 单一对象多条件() 'Select CaseSelect Case [a1]Case Is 60Call aCase ElseCall BEnd SelectExcel VBA 程序开发自学通2020-2-26第 219页 /共 508页End Sub在 Select Case 条件语中,只能对一个对象设置条件,当可能是多个条件。但多个对象指定多上条件则只能改用 IF…Then…Else 语句。Select Case 的优势在于同一对象设置多条件时,它的速度优于 IF…Then…Else 语句。IF...Then...Else 语句会计算每个 ElseIF 语句的不同的表达式,在控制结构的顶部,Select Case 语句只计算表达式一次。例如以下两个过程中,达成的结果完全一致,但在速度上有一定差异。其中 SelectCase 语句只需要判断一次变量“Select Case”符合哪一个条件,而在 IF…Then…Else语句中,有多个少 IF 和 ElseIF 就需要判断多少次。Sub 评语 1()Select Case 成绩Case Is 100MsgBox "输入错误"Case Is < 60MsgBox "不及格"Case 60MsgBox "及格"Case 60 To 80MsgBox "良"Case 81 To 99MsgBox "优"Case ElseMsgBox "满分"End SelectEnd SubSub 评语 2()IF 成绩 100 ThenMsgBox "输入错误"ElseIF 成绩 60 And 成绩 80 And 成绩 < 100 ThenMsgBox "优"ElseMsgBox "满分"End IFEnd Sub根据以上分析,读者可以进行多次测试,在实际工作中根据需求选择适当的语句。10.2.9借用 Choose 函数简化条件选择Choose 函数的作用是从参数列表中选择并返回一个值,它是一个函数,只能返回值,不能根据条件执行指定的过程或者语句,是类似于 IIF 函数,而有别于 IF…Then…语句和 Select Case 语句。Choose 函数的基本语法如下:Excel VBA 程序开发自学通2020-2-26第 220页 /共 508页Choose(index, choice-1[, choice-2, ... [, choice-n]])其中 index 必选参数,可用数值表达式或字段做参数,它的运算结果是一个数值,且界于 1 和可选择的项目数之间。如果超过可选择项目个数将产生错误结果。除 Index 以外的所有参数是可选项目,可选项目中第一个为必选参数,其余为可选参数。Index 参数必须使用数值,或者结果为数值的表示式,函数将从可选项目中返回Index 对应的值。例如:Choose(3, 1, 2, 3, 4)——返回 3,从项目表中返回第三个Choose((3 + 2) / 0.5, "A", "C", "D", , , , , , "F", "G", , , "I", "J")——返回 10,Index 参数的计算结果 10,则返回项目表中第十个字符串。相对于 IIF 嵌套或者 IF…Then…Else 语句、Select Case 嵌套使用,在根据条件返回值的要求下,Choose 函数往往是最简单的一种方式。例如对单元设置 5 种颜色,由用户选择。如果用户输入则红色,输入 2 则蓝色,输入 3 则灰色,输入 4 则棕色,输入 5 则绿色。那么如果使用 Choose、IIF、IF…Then…和 Select Case 语句来实现,将有以下四段代码:Sub 设置单元格颜色 1() 'Choose 法Dim 颜色 As Byte颜色 = Application.InputBox("请选择颜色:" & Chr(10) & "1:红色" & "2:蓝色" &Chr(10) & _"3:灰色" & "4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)Range("A1").Interior.ColorIndex = Choose(颜色, 7, 5, 15, 40, 4)End SubSub 设置单元格颜色 2() 'IIF 法Dim 颜色 As Byte颜色 = Application.InputBox("请选择颜色:" & Chr(10) & "1:红色" & "2:蓝色" &Chr(10) & _"3:灰色" & "4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)Range("A1").Interior.ColorIndex = IIF(颜色 = 1, 7, IIF(颜色 = 2, 5, IIF(颜色 = 3, 15,IIF(颜色 = 4, 40, IIF(颜色 = 5, 4, xlNone)))))End SubSub 设置单元格颜色 3() 'IF...Then...Else 法Dim 颜色 As Byte颜色 = Application.InputBox("请选择颜色:" & Chr(10) & "1:红色" & "2:蓝色" &Chr(10) & _"3:灰色" & "4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)IF 颜色 = 1 ThenRange("A1").Interior.ColorIndex = 7ElseIF 颜色 = 2 ThenRange("A1").Interior.ColorIndex = 5ElseIF 颜色 = 3 ThenRange("A1").Interior.ColorIndex = 15ElseIF 颜色 = 4 ThenRange("A1").Interior.ColorIndex = 40ElseIF 颜色 = 5 ThenRange("A1").Interior.ColorIndex = 4End IFEnd SubSub 设置单元格颜色 4() 'Select Case 法Dim 颜色 As ByteExcel VBA 程序开发自学通2020-2-26第 221页 /共 508页颜色 = Application.InputBox("请选择颜色:" & Chr(10) & "1:红色" & "Chr(10) & _"3:灰色" & "4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)Select Case 颜色Case 1Range("A1").Interior.ColorIndex = 7Case 2Range("A1").Interior.ColorIndex = 5Case 3Range("A1").Interior.ColorIndex = 15Case 4Range("A1").Interior.ColorIndex = 40Case 5Range("A1").Interior.ColorIndex = 4End SelectEnd Sub2:蓝色" &执行以上任意程序时,将弹出图 10.36 所示对话框,如果录入 1 到 5 之间任何数值,那么单元格 A1 会产生对应的颜色。图 10.36 要求用户指定 A1 需要显示的颜色以上四个过程可以产生同样的效果,但从代码的简捷性上看,Choose 函数处理的方式最好。本例文件参见光盘:..\ 第十章\按指定值设置单元格背景色.xlsm不过 Choose 函数也有它的限制。Choose 函数进行条件选择时,虽然它只返回一个选项值,但 Choose 会计算列表中的每个选择项。这一个副作者类似于 IIF 函数。或许从以下实例中更能理解这个副作用:Choose(2, 12 + 2, 356, 12 / 0)代码中 Index 参数为 2,只取第二个选项列表中第二个值,但是 Choose 会将每个选项计算一次,任意一个参数出错则会整个过程中断。见图 10.37 所示。Excel VBA 程序开发自学通2020-2-26第 222页 /共 508页图 10.37 因除数为零而程序中断如果改用 Select Case 语句则不存在此副作用,见以下代码:Sub 选择性取值()a=2Select Case aCase 1MsgBox 12 + 2Case 2MsgBox 256Case 3MsgBox 12 / 0End SelectEnd Sub10.3 循环语句循环语句(又称控制结构),它可能重复执行一系列代码,从而批量地完成工作任务。循环语句在实际工作中应用极广,且因为循环语句不可能利用录制宏产生,所以必须潜心掌握它的所语法与结构。循环语句主要包括以下几类: For Next For Each Next Do Loop Do While Loop10.3.1For Next 语句在工作中,我们可以使用 For...Next 语句去重复一个语句块,它的循环次数可以自由指定,循环的步骤也可以自由指定。1.法语详解For Next 循环语句的基本语法如下:For counter = start To end [Step step]Excel VBA 程序开发自学通2020-2-26第 223页 /共 508页[statements][Exit For][statements]Next [counter]语法列表中包括五个部分,其参数详解如下:表 10-10 For Next语法详解部分counterstartEndStepStatements描述必要参数。用做循环计数器的数值变量。这个变量不能是 Boolean 或数组元素必要参数。counter 的初值必要参数,counter 的终值可选参数。counter 的步长。如果没有指定,则 step 的缺省值为 1可选参数。放在 For 和 Next 之间的一条或多条语句,它们将被执行指定的次数其中 counter 是计数器变量,由用户声明;而 start 和 end 则表示计数器的起止范围,用户可以根据需求定义这个范围;而 step 表示步长,即计数器累加的单位。它可以是正数,也可以是负数,但是不能为 0,而不能大于 End、不能小于 Star。当循环开始后,计器数会逐步累加,累加值由步长决定;Statements 则是循环语句的核心,虽然它是可选参数,然而如果忽略此参数,所有循环都失去其意义。例如小学数学中常有 1 累加到 100 这类速算题,如果利用 VBA 循环来处理,可以零点几秒钟计算完成,代码如下:Sub 累加 1 到 100()'声明变量Dim Item As Integer, Sums As Integer'指定步长和循环的起止范围For Item = 1 To 100 Step 1'累加计数器(其中变量 Sums 初起化时值为 0)Sums = Sums + Item'执行下一个Next Item'报告最后结果MsgBox SumsEnd Sub在该过程中,循环的范围是 1 到 100,循环的步长为 1,而默认状态即为 1,所以本例中的步长也可以忽略不写,VBA 自动按 1 计算。为了获取 1 到 100 的累加值,需要使用一个中间变量,该变量本身为 0,仅仅在循环中逐个累加计数器,直到循环结束。最后直接报告变量的值即为需求的结果。也可以从最大值递减至最小值,那么步长需要使用负数。代码如下:Sub 累加 1 到 100()'声明变量Dim Item As Integer, Sums As Integer'指定步长和循环的起止范围For Item = 100 To 1 Step -1'累加计数器(其中变量 Sums 初起化时值为 0)Sums = Sums + Item'执行下一个Next ItemExcel VBA 程序开发自学通2020-2-26第 224页 /共 508页'报告最后结果MsgBox SumsEnd Sub此过程与上一个过程执行结果完全相同,仅仅在写法上不同。2.步长值正负对过程的影响在前面的实例中,步长值正负都取了相同的结果。但是在某些特情况下,步长为正数还是负数对结果有很大的影响。例如通过循环来删除工作表中前 20 行(仅演示用),那么使用正负步长会产生截然不同的结果,比对步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口中录入以下两段代码:Sub 循环删除前 20 行 A()'声明变量Dim Item As Integer, Sums As Integer'指定步长和循环的起止范围For Item = 1 To 20'逐行删除(也就是删除第一到第二十行)Cells(Item, 1).EntireRow.Delete'执行下一个Next ItemEnd SubSub 循环删除前 20 行 B()'声明变量Dim Item As Integer, Sums As Integer'指定步长和循环的起止范围For Item = 20 To 1 Step -1'逐行删除(也就是删除第一到第二十行)Cells(Item, 1).EntireRow.Delete'执行下一个Next ItemEnd Sub(3)返回工作表界面。为了查看删除效果,在 A1:B1 单元格分别输入 1 和 2,,然后将其填充到 A30,使 A1:A30 区域产生 1 到 30 的序列号;(4)按下快捷键【Alt+F8】打开“宏”对话框,首先执行第一个过程,执行结果见图 10.38 所示;(5)再执行第二个过程,其执行结果见图 10.39 所示:Excel VBA 程序开发自学通2020-2-26图 10.38 步长为正数的执行效果第 225页 /共 508页图 10.39 步长为负数的执行效果从以上两个程序的执行效果比较可以看到步长的正负对程序的影响。本例文件参见光盘:..\ 第十章\循环删除前 20 行.xlsm3.调整步长值提升循环效率在 For 循环中,对于某些特殊的循环,可以利用修改步长值来提升循环的效率。例如在工作表中列出今天所有星期日的日期。步长值为 1 的代码如下:Sub 罗列今年周日日期 A()'步长为 1Dim Item As Long, Star As Long, Ends As Long, i As Integer, tim As Longtim = TimerStar = DateSerial(Year(Date), 1, 1) + (7 - Weekday(DateSerial(Year(Date), 1, 1), 2) +1) - 1Ends = DateSerial(Year(Date) + 1, 1, 0)For Item = Star To EndsIF Weekday(Item, 2) = 7 Then i = i + 1: Cells(i, 2) = Format(Item, "yyyy-mm-dd")Next ItemMsgBox "执行时间:" & Format(Timer - tim, "0.00")End Sub该过程中步长值为 1,计数器 Item 会累加 300 多次。从单元格中的日期看,它每隔七天取出一个日期值。那么利用这个特点可以将步长设为 7,那么 IF 表达式也可以忽略了,同时计数器的累加次数会减少到七分之一。代码如下:Sub 罗列今年周日日期 B() '步长为 7Dim Item As Long, Star As Long, Ends As Long, i As Byte, tim As Longtim = TimerStar = DateSerial(Year(Date), 1, 1) + (7 - Weekday(DateSerial(Year(Date), 1, 1), 2) +1) - 1Ends = DateSerial(Year(Date) + 1, 1, 0)For Item = Star To Ends Step 10i=i+1Cells(i, 1) = Format(Item, "yyyy-mm-dd")Next ItemMsgBox "执行时间:" & Format(Timer - tim, "0.00")End Sub这两个过程在效率上差异不大,但是后者一定高于彰者,因为它循环的次数更少。Excel VBA 程序开发自学通2020-2-26第 226页 /共 508页过程中星期日的算法是:首先利用“DateSerial(Year(Date), 1, 1)”计算出今年 1月 1 日的序列值,再用 Weekday 函数计算 1 月 1 日是星期几,根据这两个信息而得出第一个星期日的日期序列。然后再利用“DateSerial(Year(Date) + 1, 1, 0)”计算今年最后一天的日期序列,而 For 循环从 Star 开始循环直到 Ends,每隔七天取出一个值并在单元格中逐个罗列出来即达成需求。本例文件参见光盘:..\ 第十章\获取周日列表.xlsm4.循环中执行多行语句在循环结构中,可以执行多行语句,两行到 200 行不等,依需求而定。下面利用实例展示循环体中执行多语句。公式为了解决发工资时的零钱问题,决定每月不放工资中的个位,而是将个位数转入下月;到了下月计算工资时,次本月的结余数(个位)累加到工资中,再取整发放,仍然结余个位到下下月……图 10.40 中 B 列即为本月应发工资,C 列是上月结余的个位数,现需计算本月每人的实发工资即取整后的值以及转入下月的个位数,并且将上月和本月结余皆为 0 者添加灰色背景。图 10.40 工资表根据以上信息,利用循环可以瞬间完成 1000 个员的工资计算。代码如下:Sub 计算工资及零钱结余()Dim i As Integer '声明变量,如果人数超过 255 需要改用 Integer 或者 LongApplication.ScreenUpdating = False '关闭屏幕更新'从第二行开始循环,直到最后一行非空行For i = 2 To Cells(Rows.Count, 2).End(xlUp).Row'实发工资等于应发工资加上月结余款除以 10 的整数的 10 倍,即工资减去个位数Cells(i, "D") = ((Cells(i, "B") + Cells(i, "C")) \ 10) * 10'本月结余工资等于应发工资加上月结余款除以 10 的余数Cells(i, "E") = (Cells(i, "B") + Cells(i, "C")) Mod 10'如果IF Cells(i, "C") = 0 And Cells(i, "E") = 0 Then Cells(i, "B").Resize(1,4).Interior.ColorIndex = 6Next iApplication.ScreenUpdating = True '恢复屏幕更新End Sub该过程中,循环的范围是不确定,它的最小值是 2,而最大值则工作表中非空行决定,行数多则循环次数多。在 For 和 Next 之间,分别执行了三句代码,包括计算应发工资和结余,以及上Excel VBA 程序开发自学通2020-2-26第 227页 /共 508页月结余与本月结余均为 0 者加黄色背景。本例文件参见光盘:..\ 第十章\计算工资中零钱结余.xlsm5.根据需求中途退出循环在个较大的范围中循环时,会消耗较多内存,而根据条件适时地退出循环则可以避免不必要的消耗。下面举一个实例说明如何在需要时退出循环。Excel 中有很多操作都会受限于合并单元格,例如排序、筛选、分列等等。在进行此类操作前,最后检查这个区域是否存在合并区单元格。本例即为开发一个自定义函数实现检查区域中是否包含合并单元格。Function 代码如下:Function MergeCell(rng As Range) '声明 Function 过程'为避免用户选择的区域过大,例如多列或者整全工作表,限制区域是 Rng 与已用区域的交集With Application.Intersect(ActiveSheet.UsedRange, rng)Dim i As Long '声明变量For i = 1 To .Count '遍历所有单元格'如果某单元格的合并区域地址与它本身地址不本同IF .Cells(i).MergeArea.Address .Cells(i).Address ThenExit For '退出循环End IFNext i'检查下一个'函数结果由计数器 i 与交集区域中单元格个数的比较结果来决定。其理论来源是:如果有合并元格,'那么循环体执行次数一定少于单元格个数,在执行中途已经中断MergeCell = i 0 Then'如果单元格地址等于存放目录的单元格地址那么激活与当前活动单元格中字符相同的工作表IF Target.Address = sht.Address Then Sheets(sht.Text).ActivateEnd IFEnd IFEnd Sub该事件过程中需要防错,因为有可能工作表删除后没有更新目录而导致单击目录时跳转失败。然后利用 IF 进行了五个条件的限制,完全符合条件则执行语句:跳转到选择的工作表。条件包括:当前工作表与公共变量 Sht 所在工作表名相同、当前选区仅仅一个单元格、单元格非空、活动单元格地址等于公共变量 Sht 的地址。(5)录入完所有代码后,返回工作表中,使用快捷键【Alt+F8】打开“宏”对话框,从中选择“目录”并执行;(6)执行程序时弹出的“目标单元格”默认指当前工作表 A1 单元格,可以任意选择工作表及单元格。见图 10.45 所示:(7)假设用户选择的单元格时工作表“周二”中的 B2,那么程序执行完毕后,进入“周二”工作表,单击 B1 单元格将弹出下拉列表,列表中有当前工作表所有工作表名,见图 10.46 所示。Excel VBA 程序开发自学通2020-2-26图 10.45 选择存放目录的单元格第 232页 /共 508页图 10.46 单元格下拉列表式目录不管从列表中选择任何表名,程序会自动跳转到工作表中,这是因为改变 B2 单元格的值时触发了工作簿事件(8)再次执行过程“目录”,将目标单元格选择“周四”中的 G20,那么此时单击“周四”工作表中的 G20 单元格才可以触发工作簿事件,从而实现工作表目录的跳转,这是共公变量“Sht”已产生变量致。虽然“周二”工作表 B2 单元格仍然保留了下拉列表,但不再有跳转功能。本例文件参见光盘:..\ 第十章\利用数据有性设建立工作表目录.xlsm以上两个过程演了 For 循环在获取工作表名中的作用,当然它还有其它很多用法,在后续章节中还会有大量的案例。10.3.3For Each Next 语法详解For Each Next 循环语句是针对一个数组或集合中的每个元素,重复执行一组语句。For Each Next 循环与 For Next 循环在语法上极相似,功能上也极相似。而且绝大多数时候可以用 For Next 语句来完成 For Each Next 的工作。但在处理对象集合时,For Each Next 也具有较多的势点,在工作中应用极广。For Each Next 语句的语法如下:For Each element In Group[statements][Exit For][statements]Next [element]For Each Next 循环语句主要包括四部分,各部分详细说明见表 10-11:表 10-11 For Each Next语法详解部分elementgroupstatementsExit For描述必要参数。用来遍历集合或数组中所有元素的变量。对于集合来说,element 可能是一个 Variant 变量、一个通用对象变量或任何特殊对象变量。对于数组而言,element只能是一个 Variant 变量。必要参数。对象集合或数组的名称(用户定义类型的数组除外)可选参数。针对 group 中的每一项执行的一条或多条语句可选参数。表示退出循环在 For next 循环中可以自由设定循环的范围,而 For Each Next 循环则无法设定范围,而是由对象的数量来决定。例如在 Sheets 集合中循环,那范围就是所有工作表,Excel VBA 程序开发自学通2020-2-26第 233页 /共 508页相当于 1 到工作表总数之间。参数 element 必须是对象,而 Group 代表该对象的集合。例如在 Sheets 对象集合中循环时,那么 element 就需要申明为工作表对象;如果在图形对象集合 Shapes 中循环时,那么 element 那要申明为图形对象 Shape……例如图 10.44 中建立工作表目录的需求,将 For Next 改为 For Each Next 语句也可以完成。代码如下:Sub 建立目录()'For Each Next 法Dim sht As Worksheet, i As IntegerApplication.ScreenUpdating = False '关闭屏幕更新,加快速度For Each sht In Sheets'遍历所有工作表IF sht.Name = "工作表目录" Then'如果工作表名字等于“工作表目录”GoTo Star '执行标签 Star 处的语句End IFNext'如果变量 i 大于工作表数量(只有不存在“工作表目录”时才会 i 大于工作表数量)Sheets.Add '添加一个新工作表ActiveSheet.Name = "工作表目录"'当前表名称等于“工作表目录”Sheets("工作表目录").Move before:=Sheets(1)'“工作表目录”移到到前面Star:Range("A:B").Clear '清除 AB 两列的值Cells(1, 1).Value = "编号"'在指定列显示编号Cells(1, 2).Value = "目录" '编号右边一列显示目录For Each sht In Sheets '遍历所有工作表IF sht.Name "工作表目录" Then '如果对象变量 Sht 的名字不等于“工作表目录”'在编号列记录编号,编号的依据是 A 列的非空单元格个数Cells(WorksheetFunction.CountA([a:a])+1,1)=WorksheetFunction.CountA([a:a])'添加超级链接,实现单元格单击时进入相应的工作表ActiveSheet.Hyperlinks.Add Anchor:=Cells(WorksheetFunction.CountA([a:a]),2), Address:="", SubAddress:="'" & sht.Name & "'!A1", TextToDisplay:=sht.Name,ScreenTip:="单击打开:" & sht.NameEnd IFNextCells(1, 1).Resize(, 2).EntireColumn.HorizontalAlignment = xlLeft '左对齐Application.ScreenUpdating = True '恢复屏幕更新End Sub在本过程中,利用对象变量 Sht 来遍历所有工作表,然后获取工作表目录。与 ForNext 循环不同的是它不能随意限制范围,例如从第二个工作表开始,所以代码中需要利用 IF 来排除“工作表目录”。10.3.4利用循环选择区域中所有负数Excel 的查找和定位功能都无法完成选择区域中所有负数,这不得不说是一个遗憾。所幸 VBA 可以轻松地达成这类需求。本例利用循环选择当前工作表中所有负数,展示 For Each Next 语句的应用技巧。具体步骤如下:(1)单击菜单【插入】\【模块】;Excel VBA 程序开发自学通2020-2-26第 234页 /共 508页(2)在模块代码窗口中录入以下代码:Sub 选择进出库记录中的负数()Dim rng As Range, rngg As Range '声明两个对象变量'将 B 列和 E 列合并为一个区域,然后在该区域与已用区域的交集中循环For Each rng In Intersect(ActiveSheet.UsedRange, Union([b:b], [e:e]))'如果 rng 对象为负数IF rng < 0 Then'如果 rngg 变量尚未初始化IF rngg Is Nothing ThenSet rngg = rng '将查到的负数单元格赋与变量 rnggElse'否则Set rngg = Union(rng, rngg) '合并变量 rng 与 rnggEnd IFEnd IFNext rngrngg.Select '选择所有负数单元格End Sub在此过程中,需地注意的事项有三点:(1)本例中要求是选择 B 列和 E 列的负数,虽然本例中其它列不存在负数,直接在 ActiveSheet.UsedRange 中进行查找也可以完成相同的任务,但是只在 B 列和 G列数据区进行循环可以节约时间、提升工作效率;(2)如果将查找的目标区域写成“Range("b3:b12,e3:e12")”也可以达成同等效果,但编写程序一定要注意通用性,当数据增减时也可以使代码适应数据变化;(3)当查找到一个小于 0 的单元格时,将它赋与变量 Rngg,直到找出所有目标单元格。但是在第一次找到小于 0 的值时,变量 Rngg 尚未始化,不能将它与变量 Rng进行合并。需要利用 IF 判断后,直接将变量 rng 赋与 Rngg,而以后查找到符合条件的目标时才可以将 Rng 与 Rngg 进行合并。执行程序后,结果见图 10.48 所示(为了便于查看,将选区添加红色背景):图 10.47 仓库进出表图 10.48 选择负数本例文件参见光盘:..\ 第十章\选择负数.xlsm10.3.5利用循环统一所有图片高度及对齐单元格在工作表中批量插入图形对象时,其高度与宽度与图形原的尺寸制约,没有提供Excel VBA 程序开发自学通2020-2-26第 235页 /共 508页用于指定统一尺寸的工具,也无法用简单的方式对齐单元格。而利用 VBA 的循环可以瞬间完成其尺寸调整及对齐单元格,具代步骤如下:(1)假设工作表中有图 10.49 所示数据与图片。使用快捷键【Alt+F11】进入 VBE界面;(2)单击菜单【插入】\【模块】进入模块代码窗口;(3)在窗口中输入以下代码:Sub 统一高度()Dim shap As Shape'声明图形对象变量'遍历所有图形对象For Each shap In ActiveSheet.Shapesshap.Height = 80 '统一高度为 80'将每个图片对齐它左下角的单元格左边线shap.Left = shap.TopLeftCell.Left'将每个图片对齐它左下角的单元格上边线shap.Top = shap.TopLeftCell.TopNext shapEnd Sub(4)光标位于过程间任意位置,并按下快捷键【F5】执行程序,工作表中所有图形对象在瞬间完成调整。调整后效果见图 10.50 所示:图 10.49混乱的图形对象图 10.50利用循环对齐图片及统一高度在该教过程中,利用图形对象变量在 Shapes 对象集合中循环,逐一调整各对象的高度及左边距与上边距。而图片的宽度则可以忽略,因为默认情况下,图形对象在调整大小时是锁定纵横比的,高度变化时宽度与会自动相应地变化。另外值得一提的是 Shapes 对象集合中包含了自选图形、任意多边形、OLE 对象、艺术字、文本框和图片对象等等。如果需要仅仅对图片进行调整,而忽略其它所有对象,那么需要改用以下代码:Sub 统一高度()Dim shap As Shape'声明图形对象变量'遍历所有图形对象For Each shap In ActiveSheet.Shapes'如果是图片IF shap.Type = 13 ThenExcel VBA 程序开发自学通2020-2-26第 236页 /共 508页shap.Height = 80 '统一高度为 80'将每个图片对齐它左下角的单元格左边线shap.Left = shap.TopLeftCell.Left'将每个图片对齐它左下角的单元格上边线shap.Top = shap.TopLeftCell.TopEnd IFNext shapEnd Sub重点在于对象的 Type 属性,如果它等于 13 则表示对象是图片。在过程中通过 IF将非图片的 Shape 对象排除即可达成需求。Shape 的可用属性见表 10-12 所示:表 10-12Shape对象的可用属性列表名称msoShapeTypeMixedmsoAutoShapemsoCalloutmsoChartmsoCommentmsoFreeformmsoGroup值-2123456msoEmbeddedOLEObject7msoFormControlmsoLinemsoLinkedOLEObjectmsoLinkedPicturemsoOLEControlObjectmsoPicturemsoPlaceholdermsoTextEffectmsoMediamsoTextBoxmsoScriptAnchormsoTablemsoCanvasmsoDiagrammsoInkmsoInkCommentmsoIgxGraphic89101112131415161718192021222324描述混和形状类型自选图形标注图批注任意多边形组合嵌入的 OLE 对象窗体控件线条链接 OLE 对象链接图片OLE 控件对象图片占位符文本效果媒体文本框脚本定位标记表画布图表墨迹墨迹批注IGX 图形本例文件参见光盘:..\ 第十章\统一图片高度及对距.xlsm10.3.6Do Loop 语法详解Do Loop 是一种循环语句,表示当条件为 True 时,或直到条件变为 True 时,重复执行一个语句块中的命令。可以使用 Do Loop 语句去运行一句或者一组语句,而它所用掉的时间是则指定Excel VBA 程序开发自学通2020-2-26第 237页 /共 508页的条件来决定。当条件为 True 或直到条件变成 True 时,此语句会一直重复,如果设计的条件一直都符合,那么它会永远循环下去,直到用户手工中断程序或者关闭 Excel程序。Do Loop 语句有两种表达形式,下面分别进行讲解。语法一:Do [{While | Until} condition][statements][Exit Do][statements]LoopDo Loop 循环各部分的含义如下:表 10-13部分conditionstatementsExit DoDoLoop语句各部分含义详解描述可选参数。数值表达式或字符串表达式,其值为 True 或False。如果 condition 是 Null,则 condition 会被当作False一条或多条命令,它们将被重复当或直到 condition 为True用于中途退出循环,可选项其中 While 与 Until 表示可以选择其中之一做为参数,它们的区别是 While 表示只如果条件为 True 则继续循环执行,而 Until 表示循环执行,直到条件这 True。很多时候它们可以互替代,在部分条件下却会有区别。下面通过一个小小的循环比较 While 与 Until 在 Do Loop 中的差异。假设某产品从上线生产到产量正常需要一个适应期,而该产品的日产量标准在1000 到 1200 之间。现在图 10.51 中统计了该产品的每日产量,求产品上线到产量正常经过了多少天。利用 While 与 Until 分别提供两种解,其代码如下:Sub 第几日产量正常 A()Dim i As Integer '声明变量i = 2 '初始化变量,从第二行开始循环Do While Cells(i, 2) = 1000'循环执行,直到大于等于 1000i = i + 1 '累加计数器LoopMsgBox i - 1 '报告计数器的值End Sub第一个过程使用 While,表示条件为 True 则继续执行,那么它的条件必是小于1000,那么遇到第一个大于或者等于 1000 的值后,条件变成 False,将中断循环;而第二个过程使用 Until,表示循环执行,直到条件变为 True,那么它的条件则需要设置为大于等于 1000,当到它第一个大于等于 1000 的值时,条件成立,那么中断循环。Excel VBA 程序开发自学通图 10.51产量表2020-2-26第 238页 /共 508页图 10.52达到正常产量的天数语法二:Do[statements][Exit Do][statements]Loop [{While | Until} condition]和语法一不同的是 While 与 Until 参数置于循环体的结尾。置于条件之前表示先检查条件表达式,然后执行循环体;而置于末尾则表示循环至少一次后才检查条件表达式,这在特殊条件下会直接影响到计数器的值。仍然以前面的实案例数据进行演示对比,两段代码如下:Sub 第几日产量正常 A()Dim i As Integer '声明变量i = 1 '初始化变量,从第一行开始循环Doi = i + 1 '累加计数器(先累加再检查条件)Loop While Cells(i, 2) = 1000'循环执行,直到大于等于 1000MsgBox i - 1 '报告计数器的值End Sub鉴于 While 与 Until 参数置于循环体的结尾时的特点是“先累加计数器后检查条件”,那么初计数器初始化的值必须小于待计算的第一个单元格行号,否则当第天就达到标准备,会产生计算错误。10.3.7在工作表中循环获取所有字体Do Loop 在工作中的实际应用时执行效率极高,本例利用 Do Loop 循环来产生Excel 支持的所有字体。实现步骤如下:(1)单击菜单【插入】\【模块】;Excel VBA 程序开发自学通2020-2-26第 239页 /共 508页(2)在模块代码窗口中录入以下代码(分别利用 Do Loop 的四种方式产生四段代码):Sub GetFontList1()On Error Resume NextDim Fonts As CommandBarComboBox, i As IntegerSet Fonts = Application.CommandBars.FindControl(ID:=1728)Do While Err = 0'只要不出现错误就一直循环执行Cells(i + 1, 2) = Fonts.List(i + 1)'在单元格罗列出字体i=i+1'累加计数器LoopEnd SubSub GetFontList2()On Error Resume NextDim Fonts As CommandBarComboBox, i As IntegerSet Fonts = Application.CommandBars.FindControl(ID:=1728)DoCells(i + 1, 1) = Fonts.List(i + 1)'在单元格罗列出字体i=i+1'累加计数器Loop While Err = 0'只要不出现错误就一直循环执行End SubSub GetFontList3()On Error Resume NextDim Fonts As CommandBarComboBox, i As IntegerSet Fonts = Application.CommandBars.FindControl(ID:=1728)Do Until Err 0'循环执行,直到出现错误时停止Cells(i + 1, 3) = Fonts.List(i + 1)'在单元格罗列出字体i=i+1'累加计数器LoopEnd SubSub GetFontList4()On Error Resume NextDim Fonts As CommandBarComboBox, i As IntegerSet Fonts = Application.CommandBars.FindControl(ID:=1728)DoCells(i + 1, 4) = Fonts.List(i + 1)'在单元格罗列出字体i=i+1'累加计数器Loop Until Err 0'循环执行,直到出现错误时停止End Sub'字体下拉列表'字体下拉列表'字体下拉列表'字体下拉列表以上四段代码分别使用 While 和 Until 置于循环体前与后来获取字体,它们在写法稍有差异,但都可以产生同样结果。(3)选择第一个过程,并按下快捷键【F5】执行过程,在 A 列将罗列出所有字体;(4)继续执行过程二、三、四,可以发现,它的效果完成一致,见图 10.53 所示:Excel VBA 程序开发自学通2020-2-26第 240页 /共 508页图 10.53 四种方法获取字体列表当然,除了 Do Loop 循环以外,用其它的方式仍然可以完成同等效果,例如使用For Next,代码如下:Sub GetFontList5() 'For Next 法Dim Fonts As CommandBarComboBox, i As IntegerSet Fonts = Application.CommandBars("Formatting").FindControl(ID:=1728)'遍历字体下拉列表For i = 1 To Fonts.ListCountCells(i, 1) = Fonts.List(i) '在单元格中逐个列出字体NextEnd Sub本例文件参见光盘:..\ 第十章\罗列字体.xlsm10.3.8计算得分累加到 1000 时的月份某球星参加过 100 场篮球比赛,在工作表中罗列了 100 场比赛的得分。现需计算他在哪一场的累计得分达到 1000 分。本例利用 For Next、For Each Next 和 Do Loop 三种方式实现,步骤如下:(1)单击菜单【插入】\【模块】;(2)在代码窗口中录入以下代码:Sub 得分累积到 1000 的场次 A() 'Do Loop 法Dim sums As Integer, i As Integer'声明变量Do Until sums >= 1000'直到积分大于等于 1000 时停止循环i=i+1'累加计数器,初始化为 1sums = WorksheetFunction.Sum(Range("B2").Resize(i, 1))Loop'报告结果MsgBox "第" & i & "场达到 1000 分", 64, "提示"End SubSub 得分累积到 1000 的场次 B() 'For Next 法Dim sums As Integer, i As Integer'声明变量For i = 1 To 1000'遍历所有场次'逐场累加得分sums = WorksheetFunction.Sum(Range("B2").Resize(i, 1))IF sums >= 1000 Then'如果大于等于 1000'报告结果Excel VBA 程序开发自学通2020-2-26第 241页 /共 508页MsgBox "第" & i & "场达到 1000 分", 64, "提示"Exit For'退出循环End IFNextEnd SubSub 得分累积到 1000 的场次 C() 'For Each Next 法Dim sums As Integer, rng As Range, i As Integer'声明变量For Each rng In Range("B2:B1001")'遍历所有场次'累加计数器i=i+1'累加得分sums = sums + rngIF sums >= 1000 Then'如果大于等于 1000'报告结果MsgBox "第" & i & "场达到 1000 分", 64, "提示"Exit For'退出循环End IFNextEnd Sub在以上四个过程中,使用了三种不同地计算方式,在执行效率上差异极小。(3)将光标定位于过程并使用快捷键【F5】执行程序,三种过程可以得到相同的结果,见图 10.55 所示:图 10.54 得分表统计图 10.55 统计积分本例文件参见光盘:..\ 第十章\计算得分累积到 1000 的场次.xlsm10.3.9利用循环产生文字动画Flash 或者网页中可以实现滚动文字,有一种炫目的感觉。在 VBA 中利用循环也可以实现,而且可以随意控制速度。具体步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口中录入以下代码:Dim 停 As Byte'声明一个公共变量Sub 字符滚动()Dim j As Integer停 =1'将变量赋值为 1Excel VBA 程序开发自学通2020-2-26第 242页 /共 508页[A1] = "四维实业公司人事报表"'设置默认字符串Do'开始循环For j = 1 To 1800'这个数字根据需要修改,它代表速度DoEvents'移交控制权,目的是让动画显示出来Next j'修改 A1 的值,将它左边第一个字符移到右边。当连续移动时就形成动画了[A1] = Mid([A1], 2, Len([A1]) - 1) & Left([A1], 1)Loop Until 停 = 0'直到变量 BBB 等于 0 时停止[A1] = ""End SubSub 停止滚动() '将变量赋值为 0,从而停止动画停 =0End Sub(3)返回工作表,单击功能区【开发工具】\【插入】\【按钮】(表单控件中的按钮);(4)在工作表中按住鼠标左键向右下拖动,从而绘出一个按钮;(5)此时 Excel 会弹出弹出一个“宏”对话框,见图 10.56 所示。在宏名列表中选择“字符滚动”;(6)同样方式添加第二个按钮,将其宏名设置为“停止滚动”;(7)将两个按钮的显示字符分修改为“开始”和“停止”;(8)单击“开始”按钮,单元格 A1 中的字符即开始从右向左滚动,见图 10.57;(9)随时单击“停止”按钮可以中止动画。图 10.56 为按钮指定宏图 10.57 用按钮控件字符串滚动在本例代码中需要注意三点:(1)利用两个过程控制变量值的,变量必须声明为公共变量;(2)在 VBA 的循环中,VBA 会占用较多的内存,而且在循环过程中它一直占用控制权,其它程序可能会暂停。类似于 Msgbox 对话框显示状态下,过程中任何程序都处理暂停一样。而在实现动画效果的循环中也一样无法单击其它按钮来停止动画,因为其它按钮此时无法获取控制权。为了让用户单击“停止”时可以执行过程“停止滚动”,需要使用 DoEvents 方法让循环暂时将控制权移动其它程序,系统处理其它的事件。同时移交控制权将会让动画效果突显出来,否由用户可能无法看到动画;Excel VBA 程序开发自学通2020-2-26第 243页 /共 508页(3)本例方式实现动画的速度与电脑硬件配置有关,所以不同电脑中执行该过程的动画效果可能大大不同。用户需要根据自己的实际情况修改 For 循环中的范围,例如将 1800 修改为 1000 或者 2500 等等,看看它在速度上有何变化。本例文件参见光盘:..\ 第十章\单元格文字动画.xlsm10.4 With 语句With 语句在前面的章节中已经多次提到,本节对 With 语句的用途、语法及常见错误进行分析。10.4.1With 语句的用途与语法With 语可以在一个单一对象或一个用户定义类型上执行一系列的语句。它的主要作用是简化代码、提升执行速度及减少变量的使用。1.简化代码例如某个对象在一个过程需要多次调用,那么 With 语句可以使编写代码时只写一次,却可以达成多次效用的效果。这对于代码的简化有着举足轻重的作用。例如对单元格添加数据有效性,不用 With 语的代码如下:Sub 对 B1 设置数据有效性 1()Worksheets("生产表").[B1].Validation.DeleteWorksheets("生产表").[B1].Validation.AddType:=xlValidateList,Formula1:="=$A$1:$A$8"Worksheets("生产表").[B1].Validation.IgnoreBlank = TrueWorksheets("生产表").[B1].Validation.InCellDropdown = TrueWorksheets("生产表").[B1].Validation.InputTitle = "提示"Worksheets("生产表").[B1].Validation.InputMessage = "数据来源是 A1:A8"Worksheets("生产表").[B1].Validation.ShowInput = TrueWorksheets("生产表").[B1].Validation.ShowError = TrueEnd Sub这代码比较容易阅读,然而在写法太于臃肿。利有和 With 简化后代码如下:Sub 对 B1 设置数据有效性 2()With Worksheets("生产表").[B1].Validation.Delete.Add Type:=xlValidateList, Formula1:="=$A$1:$A$8".IgnoreBlank = True.InCellDropdown = True.InputTitle = "提示".InputMessage = "数据来源是 A1:A8".ShowInput = True.ShowError = TrueEnd WithEnd Sub很显得 With 在简化代码中的有着可能小觑的贡献Excel VBA 程序开发自学通2020-2-26第 244页 /共 508页2.提升速度仍然以上面的两段代码稍加修改,进行比较,可以证实 With 句对循环语句可以大大的提速。Sub 对 B1 设置数据有效性 1()Dim tim As Longtim = TimerFor i = 1 To 1000Worksheets("生产表").[B1].Validation.DeleteWorksheets("生产表").[B1].Validation.AddType:=xlValidateList,Formula1:="=$A$1:$A$8"Worksheets("生产表").[B1].Validation.IgnoreBlank = TrueWorksheets("生产表").[B1].Validation.InCellDropdown = TrueWorksheets("生产表").[B1].Validation.InputTitle = "提示"Worksheets("生产表").[B1].Validation.InputMessage = "数据来源是 A1:A8"Worksheets("生产表").[B1].Validation.ShowInput = TrueWorksheets("生产表").[B1].Validation.ShowError = TrueNext iMsgBox Format(Timer - tim, "0.00") & "秒"End SubSub 对 B1 设置数据有效性 2()Dim tim As Longtim = TimerFor i = 1 To 1000With Worksheets("生产表").[B1].Validation.Delete.Add Type:=xlValidateList, Formula1:="=$A$1:$A$8".IgnoreBlank = True.InCellDropdown = True.InputTitle = "提示".InputMessage = "数据来源是 A1:A8".ShowInput = True.ShowError = TrueEnd WithNext iMsgBox Format(Timer - tim, "0.00") & "秒"End Sub两段都是对 B1 单元格添加有效性设置,为了更容易看出区别,将程序循环执行1000 次。分别执行以上两段代码,它们最后所报告的执行时间会差异两倍左右。本例文件参见光盘:..\ 第十章\利用 With 提速.xlsm3.减少变量有一种特殊的对象,无法多次引用。如果第二次调用它,那么将产生不同的对象,而不是第一次引用的对象。这主要是新建一个对象时会涉及到。通常解决这个问题是采用变量来取代它,那么以后调用这个变量即可。但程序中变量太多会对程序的执行效率产生副作用。那么剩下的方案就是使用 With 语句了。例如在工作簿中建立一个新工作表,并进行命名、移动处理。使用变量的处理方式是:Excel VBA 程序开发自学通2020-2-26第 245页 /共 508页Sub 建立总表且移至最后()Dim sht As Worksheet '声明一个工作表对象Set sht = Worksheets.Add '将新表赋与变量sht.Name = "总表"'设置新表的名称sht.Move after:=Sheets(Sheets.Count) '移动新表End Sub对于“Worksheets.Add”这类新建对象,是不能多次引用的,如果直接对对象赋值那么每个赋值都会产生一个新的对象。例如:Sub 建立总表且移至最后 2()Worksheets.Add.Name = "总表"'设置新表的名称Worksheets.Add.Move after:=Sheets(Sheets.Count)End Sub'移动新表该过程中两次“Worksheets.Add”代表了两个对象,会产生两个新工作表。如果改用 With 语句则可以完美处理以上所提到的两个问题。With 语句如下:Sub 建立总表且移至最后 3()With Worksheets.Add '创建一个工作表对象,后续可以多次调用这个对象.Name = "总表"'设置 With 对象的名称.Move after:=Sheets(Sheets.Count) '称动 With 对象到最后End WithEnd Sub该过程中 With 语句已经生成了一个新的对象,而后续的代码中可以随意调用这个对象,不再需要变量,也不会产生多个新工作表。4.语法解析通过前面的分析,With 语句对程序简化与提速都很重要,那么现在对其语法进行详细地分析,便于读者理解并应用到工作表中去。With 语句的语法如下:With Object[statements]End With其中 object 代表一个对象,而且必须是对象。例如以下语句中:Sub test1()With [a1]MsgBox .ValueEnd WithEnd Sub以上过程可以获取单元格对象 A1 的值。但是对 With 的对象改用表达式则不符合规则。例如:Sub test2()With [a1] + [a2]MsgBox .ValueEnd WithEnd Sub在上过程中,“[a1] + [a2]”是一个可计算的表达式,不是对一个对象。所以执行程序时会弹出运行时错误。而 statements 是可选参数,表示要执行在 object 上的一条或多条语句。通过它包括了 object 对象中的某个属性。而且虽然必须要包括某个属性,否则代码就失去了With 的意义。Excel VBA 程序开发自学通2020-2-26第 246页 /共 508页每个对象都有一个默认属性,在引用时可以忽略不写。但 With 与 End With 语句之间的执行语句中调用默认属性都必须指明,否则无法正常引用其属性值。例 如 前 面 的 test1 过 程 中 单 元 格 A1 的 默 认 属 性 值 就 是 Value , 而 代 码 中 的“MsgBox .Value”却无法忽略“.Value”部分。With 也可以嵌套使用,两层或者十层皆可。每层都需要有各自独立的对象和属性,在外层不能调用里面对象属性,里层也不可调用外层对象的属性。但在里层中声明对象时却可以调用外层对象。例如在以下过程中,With 有两层嵌套,而里层有两个 With语句并列:Sub With 嵌套()With [A1] '外层 With,对象是单元格With .Font '里层 With,对象是字体,它同时调用了外层的对象.Italic = True '里层 With 的执行语句,表示字体倾斜.Bold = True' MsgBox .Value '执行这一句一定会出错,因为此处只能调用里层对象的属性,而Value 属于外层End WithWith .Interior '里面 With,对象是单元格的内部属性.Pattern = xlSolid ''里层 With 的执行语句.PatternColorIndex = xlAutomatic.Color = 65535.TintAndShade = 0'.Name = "宋体" '执行此句也会出错,因为字体名称属性在前一个 With 语句中才可以调用,'如果此处一定要调用,那么需要指明其上层对象,例如改用:.Font.Name = "宋体"End WithEnd WithEnd Sub读者可以仔细比较三个 With 语句的用法。10.4.2With 语句实例With 语句对于条件语句和循环语句在使用上都更简单,但也有必要通过几个实例来加深读者对 With 语句的理解程度。1.批量生成复选框在工作表中设计一个复选框(方框中打勾的控件)并调整其大小与边距适应单元格虽然不难,但绝对是一个较费时的事。而如果批量地产生复选框,且所有复选框对齐并统一大小就成为一个难题了。但 VBA 处理这个问题却轻松自如,实现步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口中录入以下代码:Sub 批量生成复选框()Dim rng As Range'声明一个对象变量Application.ScreenUpdating = False'并闭屏幕更新,加快速度'遍历选区每一个单元格For Each rng In Selection'外层 With,简化变量 Rng 的调用With rngExcel VBA 程序开发自学通2020-2-26第 247页 /共 508页'里层 With,对象是插入的复选框With ActiveSheet.CheckBoxes.Add(.Left, .Top, .Width, .Height).Text = "合格" '设置复选框的显示文字.Value = True'呈选中状态End WithEnd WithNext rng '执行下一个Application.ScreenUpdating = True'恢复屏幕更新End Sub(3)返回工作表中,选择待添加复选框的单元格,例如图 10.58 中,按住 Ctrl键选择 B2:B2 和 D2:D5 两个区域;(4)使用快捷键【Alt+F8】打开“宏”对话框,然后执行程序“批量生成复选框”。程序执行结果见图 10.58 所示,每个单元格中都出现一个名为“合格”的复选框,且全部都与单元格对齐。图 10.58 在工作表中批量生成复选框在本例中,使用了两层 With 嵌套。其中外层的 With 可用可不用,里层的 With却必须使用,如果利用两个“ActiveSheet.CheckBoxes.Add”来替换 With 语句,那么此时的两句代码将代表两个对象,而不是同一个。另外,对于任何对象在新建时都会占用一些内存,而且屏幕一直在刷新、闪动,为了提升提序整体效率,必须使用 ScreenUpdating 属性来关闭刷新。本例文件参见光盘:..\ 第十章\批量生成复选框.xlsm2.让图形对象设计动画在工作表可以插入图形对象,如果能使用图形对象无休止的滚动岂非眩目?VBA完全可以胜任。实现步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口中输入以下代码:Sub 旋转图形()Dim i As IntegerWith Selection.ShapeRangeFor i = 1 To 10000'循环时次数,可以控件旋转时间IF i Mod 100 > 50 Then'变化大小.Adjustments.Item(1) = (i Mod 50) / 100 '控制图形的大小.IncrementRotation 1 '旋转一个单位DoEvents.Fill.ForeColor.SchemeColor = (i Mod 50) '修改填充色,不断变化DoEventsElseExcel VBA 程序开发自学通2020-2-26第 248页 /共 508页.Adjustments.Item(1) = 0.5 - (i Mod 50) / 100 '控制图形的大小.IncrementRotation 1'旋转一个单位DoEvents.Fill.ForeColor.SchemeColor = (i Mod 50)'修改填充色,不断变化DoEventsEnd IFNextEnd WithEnd Sub(3)返回工作表,单击功能区【插入】\【形状】\【太阳形】(符号: ),然后在工作表中给制一个太阳形状,见图 10.59 所示:(4)选择绘制的图形,按下快捷键【Alt+F8】打开“宏”对话框,选择“旋转图形”并执行,图形对象会自动开始向顺时针旋转。而且旋转过程中,它会不断的变化颜色及放大、缩小,图 10.60 是旋转过程中的一个截图。图 10.59 插入太阳图形图 10.60 旋转过程中的截图在该过程中,反复调用“Selection.ShapeRange”多次,利用 With 语句可以大大地简化代码,同时提升代码的编写效率与执行效率。本例文件参见光盘:..\ 第十章\旋转图形.xlsm10.4.3With 语句常见错误分析With 语句在使用上极简单,但初学者可能仍然会出现诸多问题,笔者也在初期经历了多次的出错后才积累了一些应用经验。With 语句在工作中常见的问题主要表现在三个方面:1.忘记写“End With”当 With 与 End With 之间有很多行代码时,常常会忘记写 End With,而造成图 10.61所示错误。当然,这是未养成好的代码录入习惯所造成的,解决这个问题需要严格按照本书第九章中关于“配对语句的录入方式”所讲的方式进行。2.End With 放错了位置当过程中除 With 语句外,还包括了条件语句或者循环语句等需要配对的语句时,有可能出现 End With 放错了位置的情况。Excel VBA 程序开发自学通2020-2-26第 249页 /共 508页例如在以下过程中,With 语句与 IF 语句共用,而 End IF 和 End With 两句的位置刚好错位,所以执行时会产生图 10.62 所示编译错误。Sub test()With Range("A1")IF .Value = 1 ThenRange("B2").EntireRow.Clear.Value = .Value + 1End WithEnd IFEnd Sub图 10.61 忘记写 End With 之错误图 10.62 End With 错位之错误3.里层 With 代码调用外层 With 对象的属性当过程中有多个 With 嵌套时,引用对象是需要分层级的。如果需要调用外层的对象,必须完整地录入整个对象,而不能前置小圆点去引用。例如以下过程中,在里层的 With 语句中调用单元格的 Left 属性时,不能使用前置小圆点,而需要完整的注明对象。代码中“MsgBox .Left”需要改为“MsgBoxRange("A1").Left”。Sub Test()With Range("A1")With .InteriorMsgBox .LeftEnd WithEnd WithEnd Sub同理,如果在外层需要调用里层的对象时,也需要完整地注明里层对象。10.5 错误处理语句在开发程序时,一定会出现执行错误,包括可以预料的错误、意外的错误,以及开发者故意设置的错误等等。在过程中有必要对这些错误进行处理,防止程序中断或者得到错误的运算结果。还有一种情况是重写 VBA 的错误提示,因为部分情况下 VBA 的提示可能并非实际的错误原因,用户无法从中获得有用的资讯,开发者有必要重写一个错误提示,使终端用户从中获取出错原因或者清楚正确的操作方式。本节对程序出错的原因、含义、错误捕捉设置、以及防错的方法进行一一解说。Excel VBA 程序开发自学通10.5.12020-2-26第 250页 /共 508页错误类型与原因VBA 内部对程序出错的原因有近 100 种解释,即表示错误类型有近百种。但若以大类进行分类,通常可以分为三类: 环境问题 开发者“笔误” 用户错误使用1.环境问题此处指的环境问题是指 OFFICE 应用程的环境,而这个环境又包括两方面:版本和 Excel 对象。版本问题是指使用者的 OFFICE 版本与开发者的应用程序版本不同,造成代码上的兼容性问题。例如在 Excel 2007 中录制一个排序的宏,将宏应用到 Excel 2003 就一定会出错。因为 Excel 2007 加入了新的排序方法,它只能在 Excel 2007 中运行,2003不支持。但是 Excel 2003 中录制的排序宏却可以在 2007 中正常执行。对象问题主要是指过程中涉及的工作表对象、工作簿对象、或者文件、图片等等不存在,而对该对象进行读取自然会出错。一方面可能开发者的代码在容错性上未下足功夫,另一方面也可能是用户使用不当,使代码无法读取到指向的对象。2.开发者“笔误”很多大中型插件或者系统都有 10 万或者更多的代码,难免出现一个错误。包括标点符号的多写或者漏写,以及单词拼写错误。所以代码的测试显得极为重要。3.用户错误使用错误使用也包括两类:无意和有意。例如在工作表保护状下执行了修改、汇总工作表数据的程序,工作表命名的程序中录入了非法字符,以及选择图片状态下执行了对选区进行查找、汇总之类的程序,无意中疏失使程序无法进行。而有意的错误使用,主要是基于测试的心态,看看程序如何反应。例如以下语句:Sub 加解密()Dim ans As Byteans = Application.InputBox("输入 1:加密" & Chr(10) & "输入 2:解密", , , , , , , 1)更多代码End Sub在以上过程中,因为正常情况下变量 ans 只需要 1 到 2 这个范围之间变化,那么编程时将其数据类型声明为 Byte。而用户可能会故意胡乱的输入 300 以上的或者 0 以下的数据,那么程序就会中断,产生“溢出”错误。基于以上的各种原因,程序在使用过程中一定会出现错误。那么在代码中进行防错,就显得极为重要。Excel VBA 程序开发自学通10.5.22020-2-26第 251页 /共 508页Err 对象及其属性、方法在 VBA 中提供了一个 Err 对象,用于提示关于运行时错误的信息。Err 对象的属性由错误的生成者来设置,包括 VBA 程序,代码中涉及的对象或者是程序开发者。Err 对象的属性和方法可以通过 VBA 的“自动列出成员”来获取,方法是在代码窗口中输入“Err.”,VBA 会自动弹出一个成员下拉列表,其中带有绿色图标者表示方法,其余的表示属性见图 10.63 所示。图 10.63 Err 对象的成员列表Err 的属性参见表 10-14,Err 对方法参见表 10-15.表 10-14 Err属性详解属性NumberHelpContextHelpFileSourceLastDLLErrorDescriptio含义返回或设置表示错误的数值。Number 是 Err 对象的缺省属性。可读/可写返回或设置一个字符串表达式,包含 Windows 帮助文件中的主题的上下文 ID。可读 / 可写返回或设置一个字符串表达式,表示帮助文件的完整限定路径。可读/可写返回或设置一个字符串表达式,指明最初生成错误的对象或应用程序的名称。可读写返回因调用动态链接库 (DLL) 而产生的系统错误号。只读。 在Macintosh中,LastDLLError 总是返回零返回或设置一个字符串表达式,包含与对象相关联的描述性字符串。可读/可写表 10-15 Err方法详解属性RaiseClear含义产生运行时错误清除 Err 对象的所有属性设置Err 的属性中使用最频繁的是其 Number 属性,而方法中使用频繁的是 Clear。在本书前面的章节和后面的章节都有关于 Number 属性和 Clear 方法的应用。10.5.3认识 Error 函数Error 函数返回对应于已知错误号的错误信息。VBA 内部对 VBA 的所有错误都有相应的编码,利用该编号可以模拟一个错误信Excel VBA 程序开发自学通2020-2-26第 252页 /共 508页息。Error 函数语法如下:Error [(errornumber)]其参数代表错误编码,例如编码 6 表示“溢出”错误。完整代码如下:Sub 溢出错误()Error 6End Sub执行以上过程时会弹出图 10.64 所示错误提示框:图 10.64 模拟溢出错误Error 函数和 Err.Raise 的功能类似,都可以人工设计一个错误。10.5.4罗列错误代码及含义VBA 中有近 100 个错误类型,当程序出错时会弹出对个对话框,而该对话框中简短的几字符往往无法让用户明白出错的实际原因。本例利用了 Err 的多个属性在单元格中产生错误错误、描述及错误含义详查。具体实现步骤如下:(1)单击【插入】\【模块】打开模块代码对话框;(2)在模块代码窗口中输入以下过程代码:Sub 获取所有错误类型编码及含义()Dim i As Integer, num As IntegerRange("A1:C1") = Array("错误 ID", "错误描述", "帮助主题") '一次性写三个单元格的标题For i = 1 To 1000 '在获取编号为 1 到 1000 的错误中循环On Error Resume Next '遇到错误仍然继续执行err.Raise i '人工产生一个错误,其编号随循环而递增'如果错误类型不是"应用程序定义或对象定义错误"或者编号等于 1 就提取该错误描述IF Error(i) "应用程序定义或对象定义错误" Or i = 1 Then'利用变量 num 获取 A 列第一个非空单元格的行号num = WorksheetFunction.CountA(Range("a:a")) + 1Cells(num, 1) = i '将错误编号赋与 A 列第一个非空单元格Cells(num, 2) = Error(i) '将错误描述赋与 B 列第一个非空单元格Cells(num, 3) = err.HelpContext '将错误描述赋与 C 列第一个非空单元格End IFNext iEnd Sub在以上过程中,首先使用了“On Error Resume Next”防错,表示不管遇到什么错误都继续执行下去;然后通过 Raise 方法人为产生一个错误,从而可以捕捉该错误的Excel VBA 程序开发自学通2020-2-26第 253页 /共 508页帮助主题;最后在是 A、B、C 三列中罗列出错误编号、认描述和帮助主题。(3)为了让单击工作表中的错误类型可以弹出详细的帮助主题,还需要编写工作表单击事件以调用对应的帮助主题。双击工程资源管理器中的“Sheet1”,然后输入以下单击事件代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)'只在单击第二列时和效IF Target.Column = 2 ThenDim str As StringOn Error Resume Next'防错err.Raise 1'故意生成一个错误,用于提取本电脑中帮助文件的文件夹str = err.HelpFile'获取帮助文件文件名'利用 Help 方法打开对应的帮助文件,即错误类的详细描述Application.Help CStr(err.HelpFile), Cells(Target.Row, 3)End IFEnd Sub以上过程利用 IF 限制单击事件在只有 B 列才能触发;然后人为产生一个错误,并捕捉到错误的帮助文件名字;最后利用 Help 方法打开对应的帮助主题。(4)返回工作表界面,使用快捷键【Alt+F8】打开“宏”对话框,选择“获取所有错误类型编码及含义”,执行过程后将在工作表中产生图 10.65 所示错误类型描述。(5)单击 B6 单元格“溢出”,将分打开与“溢出”相关的帮助主题,见图 10.66所示。图 10.65 错误类型认识描述及其帮助主题图 10.66 溢出错误的帮助信息此工作表可以作为日常工作中的 VBA 错误速查表。而且过程中涉及了 Err 相关的多种属性与方法,读者可以从中了解 Err 相关语法的应用。本例文件参见光盘:..\ 第十章\ VBA 内置错误类型速查表.xlsm10.5.5VBA 的错误时处理机制VBA 中有专用的防错语句:On Error 语句。它可以启动一个错误处理程序并指定该子程序在一个过程中的位置;也可用来禁止一个错误处理程序。On Error 语句主要包括三类,见表 10-16 所示:表 10-16 VBA的错误处理语句Excel VBA 程序开发自学通语句On Error GoTo lineOn Error Resume NextOn Error GoTo 02020-2-26第 254页 /共 508页用途启动错误处理程序,且该例程从必要的 line 参数中指定的 line开始。line 参数可以是任何行标签或行号。如果发生一个运行时错误,则控件会跳到 line,激活错误处理程序。指定的 line 必须在一个过程中,这个过程与 On Error 语句相同; 否则会发生编译时间错误说明当一个运行时错误发生时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行。访问对象时要使用这种形式而不使用 On Error GoTo禁止当前过程中任何已启动的错误处理程序1. On Error GoTo line本语句表示当程序错误时为 VBA 指定一个回应方式,如果有错误就跳转到指定的标签处继续运行。通常见指定的标签包括三类: 对错信息进行重新描述 指定错误的处理方式 循环执行等待消除错误下针对以上三类应用各举一个实例进行讲解。实例一:将当前选择的图片扩大到 2 倍。Excel 2003 和 2007 在图片缩放上有一些差异,开发者需要明白这一点。即 2007中缩放图片时是按锁定纵横比的前提下执行的,而 2003 中虽然图片的属性默认也是锁定纵横比,但是利用代码来进行操作时却没有按锁定纵横比的方式进行。估计微软意识到这个 BUG,在 2007 做了改进。所以在 Excel 2003 和 2007 中可以使用以下两段代码实现?Sub 将选择的图片放大到倍() 'Excel 2003 专用Selection.ShapeRange.ScaleHeight 2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍Selection.ShapeRange.ScaleWidth 2, msoFalse, msoScaleFromTopLeft ' 高 度 增 大到两倍End Sub先扩大宽度再扩大高度。Sub 将选择的图片放大到倍() 'Excel 2007 专用Selection.ShapeRange.ScaleHeight 2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍End Sub当宽度扩大后,高度也同时扩大。当然,也可以利用 IF 将两个过程综合为一个,使用可以通用于 Excel 2003 和 2007,代码如下:Sub 将选择的图片放大到两倍() '通用Selection.ShapeRange.ScaleHeight 2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍IF Application.Version * 1 < 12 Then '版本低于 12 才执行Selection.ShapeRange.ScaleWidth 2, msoFalse, msoScaleFromTopLeft ' 高 度 增大到两倍End IFEnd SubExcel VBA 程序开发自学通2020-2-26第 255页 /共 508页如果选择工作表中某个图片,执行以上代码后,那么图片是自动放大至两倍。然而,当用户选择单元格再执行过程时,将弹出图 10.67 所示对话框。显然从这个错误提示很难明白出错的真正原因,完全有必要对这个错误提示进行重新描述,让用户更清楚为何会出错。那么将 On Error GoTo line 加入代码中即可达成这种需求。代码如下:Sub 将选择的图片放大到两倍 B()On Error GoTo err ‘防错处理Selection.ShapeRange.ScaleHeight 2, msoFalse, msoScaleFromTopLeft大到两倍IF Application.Version * 1 0 Then MsgBox "只能输入 1 或者 2"'让用户选择汇总方式ans = Application.InputBox("输入 1:对选区求和" & Chr(10) & "输入 2:对选区求积", , , , , , , 1)IF ans = 1 Then'如果输入 1MsgBox WorksheetFunction.Sum(Selection) '求和ElseIF ans = 2 Then '如果输入 2MsgBox WorksheetFunction.Product(Selection) '求积End IFEnd Sub执行以上过程时将弹出一个“输入”对话框,假设用户故意输入 300,见图 10.70所示,那么程序会返回标签 Star 处,首先弹出图 10.71 所示提示,然后弹出图 10.72所示对话框要求用户再次输入数值,从而解决问题。图 10.71 确定汇总方式图 10.72 出错提示VBA 的错误处理程序有一个特点:如果在错误处理程序处于活动状态时(在发生错误和执行 Resume、Exit Sub、Exit Function 或 Exit Property 语句之间这段时间)Excel VBA 程序开发自学通2020-2-26第 257页 /共 508页又发生错误,则当前过程的错误处理程序将无法处理这个错误。所以上面的过程也只能发生一次作用,即第一次输入 300 时可以返回标签 Star 处要求用户重新输入数据,但如果用户再次输入 300,On Error 语句将不再发生作用。为了解决这个问题,可以让过程递归。修改后的代码如下:Sub 选区求和或者求积 B()Dim ans As Byte '声明一个动态变量Static i'声明一个静态变量i=i+1'将变量累加 1On Error GoTo Star: '出错时执行 Star 标签处的语句IF i > 1 Then MsgBox "只能输入 1 或者 2" '如果变量 i 大于 1(只有出程序出错而再次调用过程自身才会出错)'让用户选择汇总方式ans = Application.InputBox("输入 1:对选区求和" & Chr(10) & "输入 2:对选区求积", , , , , , , 1)IF ans = 1 Then'如果输入 1MsgBox WorksheetFunction.Sum(Selection) '求和ElseIF ans = 2 Then '如果输入 2MsgBox WorksheetFunction.Product(Selection) '求积End IFi = 0 '将静态变量还原为 0,防止正常情况下也提示“只能输入 1 或者 2”Exit Sub '退出过程Star:'标签,让过程调用自身,重新让用户输入数值选区求和或者求积 BEnd Sub如果在过程中输入以了超过 Byte 有效范围的数值,那么过程会一直循环下去,弹出对话框让用户重新输入数值,直到输入 1 或者 2 为止。本例文件参见光盘:..\ 第十章\循环执行等待消除错误.xlsm2. On Error Resume Next当错误时仍然继续执行后面的语句,而不是弹出错误提示,同时中断程序运行,那么你可以使用 On Error Resume Next。在工作中 On Error Resume Next 语句的应用频率非常高,因为实际工作表不可预料的错误很多,利用这种防错机制可以确保程序执行完毕,不会中途中断。On Error Resume Next 语句可以置于过程中的任意位置,然而通常是放在最前端。因为防错语句在最后不会产生任何作用。但最好的方式是放在需在处理错误的语句前面,而不是整个过程的最前面,以防止屏弊了不该屏弊的错误。提示:On Error Resume Next 是把双刃剑,它可让程序不在中途中断而执行所有语句,然而对于某些有价格的错误提示也被屏弊掉了。所以实际工作中应尽量多测试,对于可以预料的错误可以利用其它方式进行防范,例如“IF Err=1004 then……”等等语句来处理。而对于确实无法把握的才使用 On Error Resume Next。下面通过三个实例展示 On Error Resume Next 语句在实际工作中的应用。实例一:计算人均款项在图 10.73 所示的工作表中,需要根据预拨款和部门的人数计算人均预拨款。然而部分部门未上报人数,在编写代码时必须要防错,否将产生“除数为 0”的错误。代码如下:Sub 人均预拨款()Excel VBA 程序开发自学通2020-2-26第 258页 /共 508页'遇到错误时继续执行,防止人数未填时产生错误、中断程序On Error Resume NextDim Item As Byte '声明变量For Item = 2 To Cells(Rows.Count, 1).End(xlUp).Row'遍历所有部分Cells(Item, 4) = Cells(Item, 1) / Cells(Item, 3)'计算人均款项Next ItemEnd Sub执行以上代码时,过程会避过人数为 0 的部门,而继续计算下一部门的人均款项,见图 10.74 所示。图 10.73 预算表图 10.74 计算要均预算本例文件参见光盘:..\ 第十章\防错处理一.xlsm实例二:打印所有工作表的打印区域逐个打印当前工作簿中所有工作表的打印区域,如果某个工作表未指定打印区域PrintArea,那么程序会中断,且弹出图 10.75 所示错误。如果利用防错语句就可以让程序忽略该表,继续打印下一个工作表。完整代码如下:Sub 打印所有工作表数据()On Error Resume Next '错误时执行下一步Dim sht As Worksheet '声明一个工作表对象变量For Each sht In Sheets '遍历所有表'打印每个工作表的打印区域,如果某工作表未指定打印区域,会产生错误sht.Range(sht.PageSetup.PrintArea).PrintOutNext shtEnd Sub执行以上过程不会产生可错误,可以按顺序将已设置打印区域的所有工作表数据打印出来。如果所有工作表都未指定打印区域则程序无任何反应。图 10.75 工作表未指打印区域时产生的错误提示Excel VBA 程序开发自学通2020-2-26第 259页 /共 508页本例文件参见光盘:..\ 第十章\防错处理二.xlsm实例三:打开 C 盘 4 个工作簿打开指定目录中的工作簿时,如果工作簿不存在将会产生错误而中断程序。如果程序需要打开多个工作簿,那么后面的工作簿即使存在也无法开启,因为程序已产生致命错误。而利用防错机制可以让程序对不存在的工作簿进行记录,而继续打开其它的工作簿。完整代码如下:Sub 打开四个生产簿()On Error Resume Next'出错时继续执行下一步Dim Item As Byte, msg As String, arr()'声明三个变量是,包括一个数组变量'为数组变量赋值,等于四个工作簿的名称arr = Array(" 一 月 生 产 表 .xlsm", " 二 月 生 产 表 .xlsm", " 三 月 生 产 表 .xlsm", " 四 月 生 产表.xlsm")For Item = 0 To 3'遍历数组中所有元素(数组默认下限为 0)Workbooks.Open Filename:="C:\" & arr(Item)'逐个打开工作簿IF Err.Number = 1004 Then msg = msg & arr(Item) & Chr(10)'如果存在错误则记录当前工作簿的名称Err.Clear '清除错误设置,否则以后所有工作簿的名字都会被记录下来Next ItemMsgBox msg '报告未打开的工作表名称End Sub假设 C 盘中只在“一月生产表.xlsm”和“三月生产表.xlsm”那么执行该过程时会产生图 10.76 所示对话框,罗列出所有不存在的工作簿名称。图 10.76 罗列不存在工作簿名称在本例中,有一个地方需要特别注意:假设第一个工作簿“一月生产表.xlsm”不存在,那么在以后的循环中,不管什么时候“Err.Number = 1004”都成立。那么对于已经打开的工作簿也会将其名称罗列在对话框中,这显然不是用户期望的结果。为了防范此问题,需要利用 Err 对象的 Clear 方法将其归零,然后再继续错误编码地捕捉。本例文件参见光盘:..\ 第十章\防错处理三.xlsm3. On Error GoTo 0On Error GoTo 0 语句表示禁止当前过程中任何已启动的错误处理程序。假设在过程中有 On Error GoTo line 和 On Error Resume Next 语句,那么本语句可以使用 On Error GoTo line 和 On Error Resume Next 失效。所以 On Error GoTo 0 语句不会单独使用。如果在过程中使用了或者 On Error GoTo line 和 On Error Resume Next 语句,限制了程序出错时该如何处理,而在后续的过程中如果不再需要按该规则执行,则可以在适当的地方插入 On Error GoTo 0 语句,表示清除前面的错误处理机制。Excel VBA 程序开发自学通10.5.62020-2-26第 260页 /共 508页错误处理:错误三次则退出程序计算机的登录密码可以设置为错误 N 次即禁止登录,而 VBA 中利用错误处理规则也可以完成同等功能。现设计一个打开文件时密码错误三次则退出程序的功能。该文件为 C 盘中的“生产表.xlsx”,密码为 123。具体步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口录入以下代码:Sub 错误三次则退出()Star:'指定标签,错误时从头开始执行On Error Resume Next '错误时执行下一步Err.Clear'清除错误(不等于 On Error GoTo 0,请注意)i=i+1'累加变量,记录输入密码的次数'输入密码,在对话框中提示次数pass = Application.InputBox("请输入密码:" & Chr(10) & "您还有" & 4 - i & "次机会", "第" & i & "次输入密码", , , , , , 3)'利用前面输入的密码去打开带有密码的工作簿Application.Workbooks.Open "C:\11.xlsx", , , , pass'如果已经错误三次则提示并结束程序,您也可以加入关闭工作簿或者退出 Excel 程序等等语句IF i = 3 Then MsgBox "对不起,你已错误三次,程序即将关闭": EndIF Err 0 Then GoTo Star '如果有错误则返回标签 Star 处重新执行End Sub(3)在 C 盘存放一个打开密码为 123 的文件——生产表.xlsx;(4)光标定位于“错误三次则退出”过程任意位置处,按下快捷键【F5】执行程序,程序立即弹出图 10.77 所示对话框,在对话框中提示了用户当前是第几次输入密码,以及还剩下几次机会;(5)如果在对话框输入密码 456,并单击“确定”按钮将弹出图 10.78 所示对话框。图 10.77 第一次录入密码图 10.78 第二次录入密码(6)如果第三次输入错误的密码将弹出图 10.79 所示对话框,同时表示这是最后一次机会。如果第三次错误则弹出图 10.80 所示错误信息。图 10.79 第三次输入密码图 10.80 密码错误三次后的错误信息Excel VBA 程序开发自学通2020-2-26第 261页 /共 508页在本例中,与“循环执行等待消除错误.xlsm”工作簿中的过程“区域求和或者求积 B”在功能上相似,但实现方式上采用了完全不同的思路,不再用静态变量和过程递归,而是在过程最前端设置标签,程序中的防错语句将过程跳转到该标签即可,因过程不曾结束,变量的值不会被释放,所以动态变量也可以实现错误次数的记录。本例文件参见光盘:..\ 第十章\错误三次则退出程序.xlsm10.5.7 错误处理:多功能选区统计Excel 2003 的状态栏可以对选区的平均值、计数、计数值、最大值、最小值及求和进行统计,然而却无法同时在状态栏显示出来。它只同由用户指定一种统计类型,见图 10.81 所示;Excel 2007 对此做了改进,可以同时显示六种结果在状态栏,见图10.82 所示。然而当用户需要显示统计结果的同进还要将统计导入到单元格中那么只能利用 VBA 程序了。图 10.81 Excel 2003 调整状态栏显示信息图 10.82 Excel 2007 同时显示多项统计结果实现要例需求步骤如下:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口录入以下代码:Sub 多功能计算()Dim MSG As String, aver, rng As RangeOn Error GoTo ERR'如果程序出错,就跳转至 ERR 标签处,运行 ERR 之后的语句MSG = MSG & "计数值:" & WorksheetFunction.Count(Selection) & Chr(10)MSG = MSG & "计 数:" & WorksheetFunction.CountA(Selection) & Chr(10)MSG = MSG & "最大值:" & WorksheetFunction.Max(Selection) & Chr(10)MSG = MSG & "最小值:" & WorksheetFunction.Min(Selection) & Chr(10)On Error Resume Next '重新指定新的防错机制aver = WorksheetFunction.Average(Selection)'获取平均值'如果有错误(选区空白或者没有数值时才产生错误)则平均值等于 0,否则等于平均值MSG = MSG & "平均值:" & IIF(ERR.Number = 1004, 0, aver) & Chr(10)MSG = MSG & "合 计:" & WorksheetFunction.Sum(Selection) & Chr(10)MSG = MSG & "单元格:" & Selection.CountMsgBox "您的选区:" & Chr(10) & MSG, 64, "【友情提示】" '弹出提示Set rng = Application.InputBox("请选择存放结果的单元格", "选择单元格", , , , , , 8)IF Not rng Is Nothing Then rng(1) = MSGExit Sub'退出程序,避免运行 ERR 标签后的语句ERR:'设定一个标签,必须加冒号,但标签名不能与程序名称相同MsgBox "请选择单元格。", 64, "提示"End SubExcel VBA 程序开发自学通2020-2-26第 262页 /共 508页(3)返回工作表界面,选择数据区域,按下快捷键【Alt+F8】打开“宏”对话框,然后执行过程“多功能计算”,程序将根据选区的数据弹出图 10.83 所示统计结果;图 10.83 根据选区数据进行多功能统计(4)单击“确定”按钮后,程序会继续弹出图 10.84 所示对话框,提示用户选择存放结果的单元格。如果不需要显示在单元格,那么可以单击“取消”按钮,否则可以选择任意一个单元格存放。用户可单元格一个单元格也可以是选择一个区域,但结果只存入该区域左上角单元格。本例中选择 Sheet2 工作表的 A1 单元格,然后单击“确定”按钮,执行结果见图 10.85 所示。图 10.84 选择存放结果的单元格图 10.85 将结果存入单元格本例过程中有五个知识点需要注意:(1)程序对选区进行统计,如果用户选择了单元格以外的任何对象,那么程序将产生编码为 1004 的错误,所以需要利用防错语句 On Error GoTo ERR 提示用户选择单元格;(2)On Error GoTo ERR 仅仅对它后面的第一句代码产生作用。如果第一句没有出错,那么第二句、第三句也不可能出错;(3)利用平均值函数对选区计算平均值时,如果选区空白,或者只有文本,即使用选择对象是单元格,“WorksheetFunction.Average(Selection)”语句也同样会产生错误。此时需要改变已有的防错机制,强制平均值返回零,而非提示用户“选择单元格”。所以利用“On Error Resume Next”语句让过程继续执行,如果有错误就返回 0,否则返回平均值;(4)在选择结果存放的单元格时,用户有可能选择一个区域,那么结果会存入该区域所有单元格,在过程中有必要限制对第一个单元格赋值,其它单元格忽略。(5)如果还需要追求完善,那么可对过程继续改进。即假设用户选择了在非空单元格,那么提示用户是否覆盖原有数据,如果用户选“否”则退出程序,选择“是”则覆盖数据,这个就交给读者去思考吧。Excel VBA 程序开发自学通2020-2-26第 263页 /共 508页本例文件参见光盘:..\ 第十章\多功能选区统计.xlsm10.5.8错误处理的作用域错误处理语句 On Error GoTo line、On Error Resume Next、On Error GoTo 0 也存在作用域,不过通常只在当前过程与被调用的过程间来讨论,而非当前过程与其它不相关的过程或者模块与模块之间讨论作用范围。1.On Error GoTo lineOn Error GoTo line 的作用范围是当前过程,即它的目标标签必须在当前过程中,否则将产生“标签”未定义的编译错误。2. On Error Resume NextOn Error Resume Next 的作用范围视它的出现位置而定,当过程中调用了其它过程时,如果错误处理语句在当前过程中,且位于 Call 语句前,那么它对当前过程生效,也对其它被调用的所有过程都生效。例如以下 A、B、C 三个过程,A 过程中调用了B 和 C 过程,A 过程中的 On Error Resume Nex 语句可以屏弊 A、B 和 C 过程中所有错误。Sub A()On Error Resume NextActiveSheet.Name = ""Call BCall CEnd SubSub B()ActiveSheet.Name = ""End SubSub C()ActiveSheet.Name = ""End Sub执行过程 A,它不会产生任何错误,表示 On Error Resume Nex 语句对 A、B、C都产生了作用。如果 On Error Resume Nex 语句处于被调用的过程中,那么它只对被调用的过程生效,无法控制主程序和后面被调用的过程。例如:Sub A()Call BCall CActiveSheet.Name = ""End SubSub B()On Error Resume NextActiveSheet.Name = ""End SubSub C()ActiveSheet.Name = ""End Sub在以上三个过程中,过程 B 中的防错设置仅仅对过程 B 生效,当返回过程 A 后,Excel VBA 程序开发自学通2020-2-26第 264页 /共 508页不管是过程 A 中的错误还是被调用的过程 C 中的错误都不会处理。根据以上分析,如果三个过程都需要处理错误,那么只能主程序中设置,其调用的子过程中会延用主程序中的错误处理机制。然而当主程序与其调用的子过程中需要不同的处理方式时,那么每个子过程需要清除主程序中的防错机制,然后设置自己的处理方式。另外还有一种处理方式:On Error GoTo line 与 Resume Next 共用,这是多过程相互调用的多需要用到的一种方式。例如以下三种过程,主过程 A 调用了 B 和 C,而三个过程都具有错误,都可以引用错误处理语句 On Error GoTo err。然而事实上在执行主过程 A 时,当程序出错后会跳转到 Err 标签处执行错误处理语句,却会忽略调用的过程 B 和 C。Sub A()On Error GoTo ErrActiveSheet.Name = ""Call BCall CExit SubErr:MsgBox "不能以空文本对工作表命名"End SubSub B()ActiveSheet.Name = ""End SubSub C()ActiveSheet.Name = ""End Sub如何解决上述 BUG?可以利用 Resume Next 语句,表示继续下一步。为了让错误处理的提示更易懂,下面的三个实例中采用了不同的方式对工作表进行命名:Dim str As StringSub A()On Error GoTo errstr = "?"ActiveSheet.Name = strCall BCall CExit Suberr:MsgBox "不能以" & str & "对工作表命名"Resume NextEnd SubSub B()str = "/"ActiveSheet.Name = strEnd SubSub C()str = "*"ActiveSheet.Name = strEnd Sub在以上三个过程中,仅仅在主过程 A 中调置了防错处理,但它可以对自身和所有被调用的子过程生效。最重要的是当主过程中“ActiveSheet.Name = str”语句产生错Excel VBA 程序开发自学通2020-2-26第 265页 /共 508页误 时 , 它 会 引 发 标 签 Err 之 后 的 执 行 语 句 , 而 后 会 返 回 刚 才 出 错 的 语 句“ActiveSheet.Name = str”,紧接着执行一下句代码“Call B”,从而让三个过程中的错误信息都可以显示出来。分别如图 10.86、图 10.87 和图 10.88 所示。图 10.86 过程 A 的提示信息图 10.87 过程 B 的提示信息图 10.88 过程 C 的提示信息如果在主过程中没有“Resume Nex 语句”,将不会执行过程 B 和 C。本例文件参见光盘:..\ 第十章\错误处理的作用域.xlsm3.On Error GoTo 0On Error GoTo 0 语句不管放置在什么位置,它只能对当前过产生作用,自调用自己的过程或者被自己调用的过程都没有任何影响。在以下三个过程中,主过程指定了一个标签,当主程序和它的任何子过程产生错误时都可经跳转到这个标签后开始执行。在本例过程 B 中利用 On Error GoTo 0 语句虽然可以清除错误设置,然而它对主过程 A 及不产生任影响,所以过程的执行结果和没有加 On Error GoTo 0 时完全一样。Dim str As StringSub A()On Error GoTo errstr = "?"ActiveSheet.Name = strCall BCall CExit Suberr:MsgBox "不能以" & str & "对工作表命名"Resume NextEnd SubSub B()On Error GoTo 0str = "/"ActiveSheet.Name = strEnd SubSub C()str = "*"ActiveSheet.Name = strEnd Sub10.5.9GoSub...Return 语句GoSub...Return 语句用于在一个过程中满足条件时则跳转到设定的标签处执行,执行后再返回条件语句之后继执行。与 Goto Line 配合 Resume next 的功能相似。其语法如下:GoSub LineExcel VBA 程序开发自学通2020-2-26第 266页 /共 508页...line...ReturnGoSub Line 与 Return 必须在同一过程中,不能跳转到其它过程中。可以设置一个标签行,但有多个 GoSub Line 都指向这个标签。以下的案例可能会加深用户对 GoSub...Return 语句的理解。某成绩表中包括四个班级的成绩,为了检查是否有某班某人的成绩还没有上报,利用 VBA 对每个班级进行检查,如果某班所有人的成绩都有则在工作表名后添加“已OK”,否则对缺少成绩分数的单元格进行灰色底纹标示。完整代码如下:Sub 检查资料是否完整()'功能:逐个检查工作表,如果有空白区则用灰色标示出来,如果不存在空白区则对工作表重命名,加“已 OK”Dim rng As Range'遇到错误时继续下一步On Error Resume NextDim sht As WorksheetFor Each sht In Sheets '遍历所有工作表'将选区中所有单白单元格赋与变量 RngSet rng = sht.UsedRange.SpecialCells(xlCellTypeBlanks)rng.Interior.ColorIndex = 15 '将 Rng 填充灰色背景IF Err 0 Then GoSub Err'如果有错误则执行 Err 标签后的语句Next'循环,检查下一个单元格Exit Sub'退出程序,避免执行后面的标签行Err:'设置一个标签sht.Name = sht.Name & "(已 OK)" '将不存在空白区的工作表名添加“已 OK”Err.Clear '清除错误Return'返回到 gosub 语句之后继续执行End Sub以上过程中利用 SpecialCells 方法定位已用区域的空白单元格,如果工作表中未缺少任意学生的成绩(即已用区域没有空单元格)则会产生错误,那么通过 GoSub语句将程序引导到 Err 标签处,对工作表进行重命名,完成后再通过 Return 返回,继续检查下一个工作表。执行完毕后,其效果见图 10.90 所示:图 10.89 成绩表本例文件参见光盘:..\ 第十章\ gosub 示例.xlsm图 10.90 添加标识Excel VBA 程序开发自学通2020-2-26第 267页 /共 508页本章对 VBA 中的各种语法进行了详细地介绍,以及通过大量实例传授各种语句的应用技巧。事实上 VBA 中还有很多不常用的语句,用户需要通过实践去摸索它们的技巧,例如 Get 语句、Deftype 语句、Property Let 语句、 While...Wend 语句、Input# 语句……10.5.10开发错误处理函数在编写程序时,常常需要在多个过程中编写进行类似的防错代码。为了简化程序可以编写一些函数,在函数中设置好所有防错机制,并在其它程序中调用即可。本节开发四个关于错误处理的函数:IsSht、IsWkb、IsCom 和 IsOK。1.判断工作表是否存在开发名为“IsSht”的自定义函数,用于检测当前工作簿是否存在某工作表。代码如下:'声明一个 Function 过程,判断工作表是否存在Function IsSht(ShtName As String) As Boolean'防错On Error Resume Next'声明一个工作表对象Dim sht As Worksheet'将名为 ShtName 的工作表赋与对象变量 Sht,'如果存在名为 ShtName 的表则不会出错,否则会出错Set sht = Sheets(ShtName)'您的结果由是否存在错误来决定,没有错误则表示已有 SheName 工作表IsSht = (Err = 0)End Function该函数以判断指定名称的工作表是否存在,具有一个必选参数,结是为 True 或者 False.可以利用以下过程测试该函数:Sub 添加工作表()'调用函数,判断是否存在“AA”工作表,不存在新建一个表且命名为“AA”IF Not IsSht("AA") ThenWith Sheets.Add.Name = "AA"End WithEnd IFEnd Sub在过程中首先利用 IsSht 函数判断表为“AA”的工作表中否在,不存在则新建,且命名为“AA”。2.判断工作簿是否存在开发一个判断是否已经打开批定名称工作簿的自定义函数,若已打开则返回 True,否则返回 False。代码如下:'声明一个 Function 过程,判断工作簿是否存在Function IsWkb(Wkb As String) As Boolean'防错On Error Resume NextExcel VBA 程序开发自学通2020-2-26第 268页 /共 508页'声明一个工作簿对象Dim Wkbs As Workbook'将名为 wkb 的工作簿赋与对象变量 Wkbs,'如果存在名为 Wkb 的工作簿则不会出错,否则会出错Set Wkbs = Workbooks(Wkb)'函数的结果由是否存在错误来决定,没有错误则表示已有 Wkb 工作簿IsWkb = (Err = 0)End Function该函数可以判断指定名称的工作簿是否打开,具有一个必选参数,结是为 True或者 False。可以利用以下过程测试该函数:Sub 打开工作簿()'调用函数,判断是否已经打开“AA.xlsx”工作簿,没有则打开"C:\AA.xlsx"IF Not IsWkb("AA.xlsx") ThenWorkbooks.Open "C:\AA.xlsx" '打开工作簿End IFEnd Sub3.判断批注是否存在开发一个判断单元格是否有批注的函数,若有批注则返回 True,否则返回 False。代码如下:'声明一个 Function 过程,判断工作簿是否存在Function IsCom(rng As Range) As Boolean'防错On Error Resume Next'设置单元格的批注的可见性,但并不改变其状态'如果不存在批注则会产生错误rng(1).Comment.Visible = rng(1).Comment.Visible'函数的结果由是否存在错误来决定,没有错误则表示已具有批注IsCom = (Err = 0)End Function该函数可以判断单个单元格是否有批注,具有一个必选参数,结是为 True 或者False。可以利用以下过程测试该函数:Sub 读取批注()'读取批注前调用自定义函数判断是否存在IF IsCom([a1]) Then MsgBox [a1].Comment.TextEnd Sub在读取单元格的批注前调用自定义函数判断是否存在批注。4.判断单元格是否处于可操作状态有很多操作都受工作表保护和单元格合并影响,例如筛选、排序、填充等等。只要工作表保护或者区域具有合并单元格,那么过程执行时一定会出错。现开发一个函数对区域进行判断,如果返回 True 则表明可操作。完整代码如下:'声明一个 Function 过程,判断区域是否处理可操作状态'可操作包括“排序”、“筛选”、“填充”等等受单元格合并及工作表保护影响的所有操作Function IsOK(rng As Range)'函数的值由工作表受保护状态与单元格区域合并属性两个值的乘积决定Excel VBA 程序开发自学通2020-2-26第 269页 /共 508页'只要两者皆为 False 时,相加才会等于 0,'函数结果为 true 表示工作表未保护,也不存在合并区域IsOK = (rng.Parent.ProtectContents + rng.MergeCells = 0)End Function'此处 Parent 代表工作表以上过程用于判断区域(可以是当前表也可以是其它工作表)是否受保护及存在合并单元格,有一个必选参数,结果为 True 或者 False。可以利用以下过程测试该函数:Sub 填充 A1 到 A7()'检查工作表中指定区域是否 OKIF IsOK(Sheets(2).Range("a1:a7")) Then'填充Sheets(2).[a1].AutoFillType:=xlFillDefaultEnd IFEnd SubDestination:=Sheets(2).Range("A1:A7"),不管当前表是否 Sheets(2)都可以进行操作。在测试时读者应尽量在保护与不保护或者合并与不合并下进行测试、比较。本例文件参见光盘:..\ 第十章\错误处理函数.xlsmExcel VBA 程序开发自学通2020-2-26第 270页 /共 508页第十一章 Excel 常见对象的应用技巧VBA 中有两百多种对象,掌握 VBA 对象的属性与方法是成为 VBA 高手的必经之路。本例通过大量的实例对 VBA 中常见对象进行实例应用演示,从而提高读者驾驭对象的能力。本章要点: Application 应用案例 Range 对象应用案例 Names 对象应用案例 Comments 对象应用案例 Sheets 对象应用案例 Workbooks 对象应用案例 Windows 对象案例11.1 Application 应用案例Application 对象代有 Excel 应用程序,本节从多方面对该对象进行实例演示。11.1.1选区拼写检查〖案例要求〗:对选区中所有单词进行拼写检查,对错误者突出显示。〖知识要点〗:CheckSpelling〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 单词检查()Dim rng As Range'遍历选区与已用区域的交集For Each rng In Application.Intersect(Selection, ActiveSheet.UsedRange)'首先将单词转换成首字母大写,然后在默认词典中检查,如果不存在IF Not Application.CheckSpelling(StrConv(rng, vbProperCase)) Then'将单元格背景设置为灰色rng.Interior.ColorIndex = 15End IFNextEnd Sub在本过程中,要求仅仅检查单词书写是否正确,忽略大小写问题,所要利用StrConv 函数将单元格的所有单词规范化,转成首字母大写再进行拼写检查。(3)返回工作表,选择工作表中所有单词;(4)使用快捷键【Alt+F8】执行过程“单词检查”,执行过程前后的工作表状态见图 11.1 和图 11.2 所示:Excel VBA 程序开发自学通图 11.12020-2-26第 271页 /共 508页图 11.2待检查拼写的单词对错误单词着色〖语法补充〗:(1)Application.CheckSpelling 方法用于对单个单词进行拼写检查。其语法如下:Application.CheckSpelling(Word, CustomDictionary, IgnoreUppercase)三个参数的含义见表 11-1 所示:表 11-1CheckSpelling参数详解名称必选/可选数据类型Word必选StringCustomDictionary可选VariantIgnoreUppercase可选Variant描述(仅应用于 Application 对象)要检查的单词一个字符串,它表示自定义词典的文件名,如果在主词典中找不到单词,则会到此词典中查找。如果省略此参数,则使用当前指定的词典如果为 True,则 Excel 忽略所有字母都是大写的单词。如果为 False,则 Excel 检查所有字母都是大写的单词。如果省略此参数,将使用当前的设置在本例中,忽略了第二和第二参数,表示使用系统默认词典,且使用默认方式处理大小写问题。(2)CheckSpelling 方法不能单独使用,需要前置对象 Application 才可以正常使用。本节关于 Application 对象的所有实例代码请参见光盘:..\ 第十一章\Application.xlsm11.1.2调用工作表函数〖案例要求〗:对选区数据汇总。〖知识要点〗:WorksheetFunction〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 汇总选区()'先检查选择的对象是否是单元格IF TypeName(Selection) = "Range" Then'如果是单元格则汇总Excel VBA 程序开发自学通2020-2-26第 272页 /共 508页MsgBox Application.WorksheetFunction.Sum(Selection)End IFEnd Sub在本过程中,对选区进行计算之前首先判断选择对象是否单元格。因为用户可能选择了图片或者图表等等其它对象状态下运行本过程。加入 IF 判断对象的类型可以防错。在 VBA 中可以利用循环对区域中所有数据汇总,然而当区域较大时,循环的方式效率太低,而通过 Application.WorksheetFunction 方法调用工作表函数则可以一步完成。(3)返回工作表,选择待计算的数据区域,利用快捷键【Alt+F8】打开宏对话框,选择并执行执行过程“汇总选区”,程序将弹出选区的计算结果。如果选择的对象不是单元格,那么执行程序时不会产生任何反应。〖语法补充〗:(1)Application.WorksheetFunction 可以返回一个 WorksheetFunction 对象,调用工作表函数。(2)WorksheetFunction 属于 Application 对象的属性,但是在书写时可以忽略Application,只写 WorksheetFunction 即可。(3)WorksheetFunction 方式只能调用大部分工作表函数,部分函数无法调用。因为 VBA 自身提供了类似的函数替代,例如:Sqrt 函数、Int 函数、Abs 函数、Cell 函数等等。其中 VBA 中的 Spr、Int 和 Rnd分别取代了 Sqrt 函数、Int 函数和 Abs 函数,而 Cell 函数的功能则可以通过获取对象的属性来实现。11.1.3切换鼠标形状〖案例要求〗:将工作表中的十字光标恢复为箭头型光标。〖知识要点〗:Cursor〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 恢复箭头光标()Application.Cursor = xlNorthwestArrowEnd Sub(3)按下快捷键【F5】执行该过程,返回工作表后,光标在工作表中移动时会显示箭头形状,而非十字型。图 11.3 是执行过程前后光标的对比。Excel VBA 程序开发自学通2020-2-26图 11.3第 273页 /共 508页改变光标前后〖语法补充〗:(1)Application.Cursor 属性用于改变工作表中光标的形状,它有四个可选常量,常量与光标样式对应关系见表 11-2:表 11-2Cursor常量与形状XlMousePointer 常量形状xlDefaultxlIBeamxlNorthwestArrowxlWait(2)当宏停止运行时,Cursor 属性不会自动重设。但是 Excel 重启后光标会返回十字形。(3)Cursor 必须使用前置对象 Application。11.1.4计算表达式〖案例要求〗:将单元格中的文字表达式回转换成值,本例根据图 11.4 所示产品规格计算体积。〖知识要点〗:Evaluate〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 将表达式转换成值()'声明一个变量,用于循环区域Dim rng As Range'遍历待计数区域For Each rng In Range("A2:A4")'在单元格的右边一个单元格返回计算结果rng.Offset(0, 1) = Application.Evaluate(rng.Text)'计算下一个Next rngEnd Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将表达式转换成值”,A 列中每个表达式瞬间转换成值,见图 11.5 所示。图 11.4产品规格图 11.5将表达式转换成值Excel VBA 程序开发自学通2020-2-26第 274页 /共 508页〖语法补充〗:(1)Application.Evaluate 方法用将一个名称转换为一个对象或者一个值。它的具体语法如下:Application.Evaluate(Name)其中参数 Name 可以是用户定义的名称,也可以是一个运算表达式。(2)也可以利用一个公式做为参数,例如:Application.Evaluate("Sqrt(sum(a1:b2,D10+5)/10)")也可以在参数中使用变量,例如:Sub 开方()Item = 25'为变量赋值MsgBox Application.Evaluate("Sqrt(" & Item & ")")End Sub'利用变量与函数构造表达式(3)Evaluate 可以单独使用,忽略其对象 Application。11.1.5禁用程序运行时弹出警告框〖案例要求〗:将 A 列中相同且相邻的单元格合并,合并时不能弹出提示框。〖知识要点〗:DisplayAlerts〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 合并 A 列相同且相邻的单元格()Dim rng As Range, rg As Range, rngs As RangeApplication.DisplayAlerts = False'禁止提示'提取 A 列与已用数据区域的交集Set rngs = Application.Intersect(ActiveSheet.UsedRange, [a:a])Set rg = rngs(1) '将交集第一个单元格赋与变量 rgFor Each rng In rngs.Offset(1, 0).Resize(rngs.Count, 1) '在交集向下偏移一行的区域中循环IF rng rng.Offset(-1, 0) Then '如果对象变量 rng 与其上一个单元不相等Range(rg, rng.Offset(-1, 0)).Merge '合并对象变量前面的相同数据区域Set rg = rng '重新指定对象变量End IFNextApplication.DisplayAlerts = True'还原提示rngs(1).Select'选择原选区中第一个单元格End Sub在本过程中首先将 DisplayAlerts 属性设置为 False,可以禁止合并非空单元格时弹出警告框。在过程执行完毕时再将该属性还原为 True。(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并 A 列相同且相邻的单元格”,图 11.6 中所有相邻且相同的单元格瞬间合并,结果见图 11.7 所示。Excel VBA 程序开发自学通图 11.62020-2-26省市列表第 275页 /共 508页图 11.7合并相同且相邻的单元格〖语法补充〗:(1)Application.DisplayAlerts 属性用于控制执行宏时是否弹出警告框。当值为True 时允许弹出警告框,否则禁止弹出警告框。(2)在 Excel 中可以弹出警告框的操作很多,例如合并非空单元格,删除非空工作表、未保存在退出程序、覆盖已有工作簿、将数据移动到非空区域等等。(3)DisplayAlerts 可以单独使用,忽略其对象 Application。11.1.6调整计算方式〖案例要求〗:每行插入空行。〖知识要点〗:Calculation〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 隔行插入行()Dim i As Integer, tim As LongApplication.Calculation = xlManual '将计算方式改为手动tim = Timer'记录当前时间'遍历已用区域For i = ActiveSheet.UsedRange.Rows.Count To 2 Step -1Cells(i, 1).EntireRow.Insert '每行前插入空行Next iApplication.Calculation = xlAutomatic'将计算方式改为自动MsgBox Format(Timer - tim, "0.00") & "秒" '记录时间 10 秒End Sub(3)假设工作表中在 1000 行数据,每行数据最后都在一个公式汇总该行的数据,那么插入以上代码,每行前插入一个空行,在笔者的电脑上时间约 6 秒;如果将Calculation 语句删除再执行过程,那么它的执行时间达到 10 秒钟,且公式越复杂,该执行时间越长。〖语法补充〗:(1)Application.Calculation 属性代表计算模式,它有三个可选项,三个内置常Excel VBA 程序开发自学通2020-2-26第 276页 /共 508页量及其含义见表 11-3 所示:表 11-3Calculation常量列表名称xlCalculationAutomaticxlCalculationManual值-4105-4135xlCalculationSemiautomatic2描述Excel 控制重新计算用户请求时进行计算Excel 控制重新计算,但忽略表中的更改(2)只要对工作表编辑,工作表中的每个公式都会重新运算一次,从而浪费程序的执行时间。所以在开发 VBA 过程时,需要将 Calculation 属性设置为手动,程序执行完毕后再恢复即可。如此可以避免不必要的公式计算。当然工作表中不存在任何公式时可以例外。(3)Calculation 可以单独使用,忽略其对象 Application。11.1.7罗列最近使用过的文件〖案例要求〗:单击 Excel 2007 的 Office 按钮时,可以看到图 11.8 所示的“最近使用的文档”,根据用户的设置不同,该文件的数量也有所差异。而在 Excel 2003 中,“最近使用的文档”罗列在文件菜单中。现要求将最近打开过的所有文件名包括路径罗列在工作表中,且需要显示其完整路径。〖知识要点〗:RecentFiles〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 罗列最近打开过的文件()Dim i As Byte'遍历所有文件For i = 1 To Application.RecentFiles.Count'在单元格中罗列每个文件的详细路径与文件名Cells(i, 1) = Application.RecentFiles(i).PathNextEnd Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“罗列最近打开过的文件”,将在工作表 A 列中罗列出所有历史文件列表。图 11.8 是 Office按钮中的显示效果,而图 11.9 是 VBA 过程的执行结果Excel VBA 程序开发自学通2020-2-26图 11.8第 277页 /共 508页Office 按钮下拉菜单中的文档列表图 11.9VBA 过程列出的文档列表〖语法补充〗:(1)Application.RecentFiles 表示最近打开过的文件。(2)利用 Item 可以访问最近打开过的文件的每个子对象,而配合 Open 方法可以将某个位置的文件打开。例如打开最过打开的文档列表中第三个文件打开,可以使用以下语句:Application.RecentFiles(3).Open(3)RecentFiles 必须添加前置对象 Application 才可以使用。Excel VBA 程序开发自学通11.1.82020-2-26第 278页 /共 508页查找并打开文件〖案例要求〗:让用户选择文件,然后打开所选文件。〖知识要点〗:FindFile〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 打开文件()'弹出一个对话框,让用选择选择待打开的文件,如果选择了文件则打开,否则退出程序IF Not Application.FindFile Then EndEnd Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开文件”,将弹出一个“打开”对话框,可以从对话框中选择一个或者多个文件,VBA会将用户选择的文件打开。如果用户单击“取消”按钮,则直接退出程序。〖语法补充〗:(1)Application.FindFile 方法用于显示“打开”对话框,用户从对话框可以浏览任意文件夹中的任意个文件,VBA 可以将选择的所有文件打开。(2)FindFile 不能单独使用,必须配合前置对象 Application。11.1.9建立文件目录〖案例要求〗:将文件夹中的文件建立目录,需要包含路径。〖知识要点〗:FileDialog〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 建立文件目录()'声明变量Dim i As Integer, FilesCount As Integer'弹出一个打开文件的对话框With Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = True'多选.Show'显示对话框FilesCount = .SelectedItems.Count '统计用户选择的文件数量'遍历所有选择的对象For i = 1 To .SelectedItems.Count'将选择的文件名输出到单元格中Cells(i, 1) = .SelectedItems(i)NextEnd WithEnd Sub在该过程中“.AllowMultiSelect = True”语句表示允许用户同时选择多个文件,甚至可以使用快捷键【Ctrl+A】选择所有文件。(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“建立文件目录”,将弹出一个“打开文件”对话框。假设仅选择部分文件,见图 11.10所示,当单击“打开”按钮后,程序会将选择的对象全部罗列在工作表 A 列中,见图Excel VBA 程序开发自学通2020-2-26第 279页 /共 508页11.11 所示。图 11.10图 11.11选择待建立目录的工作簿在工作表建立文件目录〖语法补充〗:(1)Application.FileDialog 属性可以返回一个 FileDialog 对象,该对象表示文件对话框的实例。它的具体语法如下:Application.FileDialog(fileDialogType)其参数是代表对话框类型的常量,有以下四种类型可选:表 11-4对话框类型常量表名称msoFileDialogFilePicker值3msoFileDialogFolderPicker4msoFileDialogOpen1描述“文件选取器”对话框“文件夹选取器”对话框“打开”对话框Excel VBA 程序开发自学通2020-2-26msoFileDialogSaveAs第 280页 /共 508页2“另存为”对话框(2)FileDialog(msoFileDialogOpen)仅仅让用户选择文件,并返回文件名与路径,并不能实际打开文件对象,如果配合 Workbooks.Open 方法就可以打开所有选择的文件对象。(3)FileDialog 必须配合前置对象 Application 才可以使用。11.1.10定制程序标题〖案例要求〗:将 Excel 默认标题“Microsoft Excel”修改为“人事系统”。〖知识要点〗:Caption〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 修改标题()Application.Caption = "人事系统"End Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“修改标题”,将 Excel 的默认标题将修改为“人事系统”,见图 11.12 所示。图 11.12修改默认的程序标题〖语法补充〗:(1)Application.Caption 属性返回或设置一个 String 值,它代表出现在 MicrosoftExcel 主窗口标题栏中显示的名称。如果使用 Msgbox 语句可以获取当前的标题字符串,而修改标题则用等号直接赋值即可。(2)如果使用以下语句,无法取消 Excel 的标题,反而是恢复默认的标题“Microsoft Excel”:Application.Caption = ""如果需要 Excel 不显示任何标题,简单的方式是利用空格做为标题。(3)Caption 必须配合前置对象 Application 才可以使用。11.1.11打开指定应用程序〖案例要求〗:打开内置的计算器。Excel VBA 程序开发自学通2020-2-26第 281页 /共 508页〖知识要点〗:ActivateMicrosoftApp〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 打开计算器()'打开 Microsoft 内置应用程序Application.ActivateMicrosoftApp Index:=0End Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开计算器”,程序会立即打开 Excel 计算器,见图 11.13 所示。图 11.13打开内置计算器〖语法补充〗:(1)Application.ActivateMicrosoftApp 方法可以激活一个 Microsoft 应用程序。如果该应用程序已经处于运行状态,则本方法激活的是正在运行的应用程序。如果该应用程序不处于运行状态,则本方法将启动该应用程序的新实例。例如已在打开一个WORD 文件,那么本方法只能使 Word 窗口激活,若未打开 Word 程序,则可以开启一个新 Word 文档。它的具体语法如下:Application.ActivateMicrosoftApp(Index)其中参数 Index 代表应程序名称,可以使用常量或者值。内置常量与见表 11-5 所示:表 11-5内置应用程序常量名称calcxlMicrosoftWord值01xlMicrosoftPowerPoint2xlMicrosoftMailxlMicrosoftAccessxlMicrosoftFoxProxlMicrosoftProjectxlMicrosoftSchedulePlus34567描述计算器Microsoft Office WordMicrosoft OfficePowerPointMicrosoft Office OutlookMicrosoft Office AccessMicrosoft FoxProMicrosoft Office ProjectMicrosoft Schedule Plus(2)ActivateMicrosoftApp 可以打开的应程序极其有限,如果需要打开更多的应用程序,应该使用 Shell 方法。例如打开 DOS 系统,可以使用以下过程:Sub 显示 Dos 界面()Excel VBA 程序开发自学通2020-2-26第 282页 /共 508页Shell "cmd.exe"End Sub显然它较 ActivateMicrosoftApp 方法灵活很多,在后续开发插件时还会涉及 Shell的大量应用。(3)ActivateMicrosoftApp 必须配合前置对象 Application 使用。11.1.12新建一个带有 7 个工作表的工作簿〖案例要求〗:新建一个工作簿,让其默认带有 7 个工作表。〖知识要点〗:SheetsInNewWorkbook〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 新建一个带有 7 个工作表的工作簿()'设置默认新工作簿的默认工作表数量为 7Application.SheetsInNewWorkbook = 7'新建一个工作簿Workbooks.AddEnd Sub该过程首先改变新工作簿中默认工作表数 3 为 7,然后新建一个工作簿。(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建一个带有 7 个工作表的工作簿”,程序会新建一个带有 7 个工作表的工作簿。〖语法补充〗:(1)Application.SheetsInNewWorkbook 属性可以返回或设置 Excel 自动插入到新工作簿中的工作表数目。其范围在 1 到 255 之间。(2)如果需要以后新建工作簿时仍然是默认值 3,那么需要过程的最后加一句代码,将该值设置为 3。(3)SheetsInNewWorkbook 必须配合其前置对象 Application 同时使用。11.1.13在指定时间提示行程安排〖案例要求〗:在下午 13:30 时提示“会议时间到”,并在 10 分钟后关闭工作簿。〖知识要点〗:OnTime\、Quit〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 会议提示()'设置 13:30 提示开会Application.OnTime TimeValue("13:30:00"), "提示"'设置 13:40 退出工作簿Application.OnTime TimeValue("13:40:00"), "关闭工作簿"End SubSub 提示()[a1] = "会议时间到!请准时参加。"'工单元格产生提示Range("A1").Font.Size = 50'为了醒目,将单元格字体加大到 50Columns("A:A").ColumnWidth = 148'将 A1 列宽加大Excel VBA 程序开发自学通2020-2-26第 283页 /共 508页Rows("1:1").RowHeight = 70'将 A1 行高加大End SubSub 关闭工作簿()'关闭警告提示 ,否则会询问是否保存工作簿Application.DisplayAlerts = False'退出 ExcelApplication.QuitEnd Sub第一个过程属于主过程,用于设置某个时间执行某个程序,其它两个过程则为被调用的过程,会在指定的时间执行。(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“会议提示”,等到 13:30 后将会在工作表 A1 显示出 “会议时间到!请准时参加。”,而10 分钟后会自动关闭 Excel 应用程序。〖语法补充〗:(1)Application.OnTime 方法可以安排一个过程在将来的特定时间运行(既可以是具体指定的某个时间,也可以是指定的一段时间之后)。它的语法如下:Application.OnTime(EarliestTime, Procedure, LatestTime, Schedule)四个参数的含义见表 11-6 所示:表 11-6EarliestTimeProcedureLatestTime必选/可选必选必选可选Schedule可选名称OnTime参数详解描述希望此过程运行的时间要运行的过程名过程开始运行的最晚时间如果为True,则预定一个新的OnTime 过程。如果为False,则清除先前设置的过程其中第二参数用于指定待运行的过程名称,在指定过程名时必须使用引号;第三参数使用 Now + TimeValue(time)形式可安排从现在开始计时经过一段时间之后运行某个过程,而使用 TimeValue(time)形式则可安排某个过程只在指定的时间执行。(2)如果需要中途取消计划的任务,那么可以将第四个参数设为 False:Application.OnTime TimeValue("13:30:00"), "提示", , False(3)如果需要在 30 秒钟后关闭工作簿,使用以下过程:Sub 会议提示()Application.OnTime Now + TimeValue("00:00:30"), "关闭"End SubSub 关闭工作簿()Application.DisplayAlerts = FalseApplication.QuitEnd Sub(4)Application.Quit 方法用于退出 Excel,它有别于 Workbooks.Close 方法。Workbooks.Close 方 法 是 关 闭 工 作 簿 , 但 应 用 程 序 仍 然 处 于 打 开 状 态 , 而Application.Quit 则关闭工作簿的同时退出 Excel 应用程序。11.1.14模拟键盘快捷键〖案例要求〗:打开高级选项。Excel VBA 程序开发自学通2020-2-26第 284页 /共 508页〖知识要点〗:SendKeys〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 打开高级选项()'模仿键盘操作: Alt -F + i + 四个下箭头Application.SendKeys "%fi{DOWN 4}"End Sub(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开高级选项”,程序立即会开启 Excel 的高级选项。功能等能于快捷键【Alt+F+I+↓+↓+↓+↓+↓】(↓表示键盘中的下箭头)。〖语法补充〗:(1)Application.SendKeys 方法可以将一个或多个按键消息发送到活动窗口,模仿键盘操作。其语法如下:expression.SendKeys(Keys, Wait)第一参数 string 表示要发送的按键消息;第二参数 wait 是可选参数,如果为 True,则 Excel 会等到处理完按键后将控件返回给宏;如果为 False,则继续运行宏而不等到处理完按键。(2)Application.SendKeys 方法发送的信息包括快捷键和文字信息。例如在使用快键复制 A1 的值到 B1,那么可以使用以下过程。Sub 复选 A1 的值到 B1()[a1].SelectApplication.SendKeys "^c{RIGHT}^v"End Sub首先激活 A1,然后向 Excel 发送快捷键【Cltr+C】表示复制单元格,然后再用右箭头激活 B1,最后使用【Ctrl+V】粘贴数据。如果是发送字符到 A1 单元格,那么可以使用以下过程:Sub 在 A1 输入短句()[a1].SelectApplication.SendKeys "{f2}how are you?~"End Sub执行以上过程后,A1 中的短句可能如图 11.14 所示,也可能如图 11.15、图 11.16所示,这取决于用户使用的输入法以及不同输入法中定义的词组。如果在英文状态下执行程序,会产生相同的结果,如果多人使用不同输入法,或者相同输入法但定义的词组不同也可以产生不同的结果。图 11.14 英文状态下输入图 11.15 五笔状态下输入图 11.16 拼音状态下输入(3)在 VBA 中,可以调用的、对应键盘上指定的功能按键包括以下表中的项目:表 11-7 SendKeys中的代码及其对象的功能按键按键代码按键代码Excel VBA 程序开发自学通BACKSPACEBreakCaps LockClearDelete 或 Del向下键EndEnter(数字小键盘)EnterEscHelpHomeIns2020-2-26{BACKSPACE} 或{BS}{BREAK}{CAPSLOCK}{CLEAR}{DELETE} 或{DEL}{DOWN}{END}第 285页 /共 508页向左键{LEFT}Num LockPageDownPageUp{NUMLOCK}{PGDN}{PGUP}Return{RETURN}向右键Scroll Lock{RIGHT}{SCROLLLOCK}{ENTER}Tab{TAB}~(波形符){ESCAPE} 或{ESC}{HELP}{HOME}{INSERT}向上键{UP}F1 到 F15{F1} 到 {F15}ShiftCtrlAlt+(加号)^(插入符号)%(百分号)除上表外,所有英文字母和数字也可以通过 SendKeys 方法发送,每个键刚好对应其自身。在上表,中最后三个用组合键,它们不能单独使用,而是与其它的字符配合完成一个快捷键的功能。例如:"^1"——对应快捷键【Ctrl+1】,表示打开单元格格式对话框"%ti"——对应快捷键【Alt+T+I】,表示打开加载宏对话框"+{F2}"——对应快捷键【Shift+F2】,表示在活动霎单元格括入批注使用组合键时需要注意以下几点点:Shift、Ctrl、Alt 三个组合键以外的功能键需要添加花括号,从而对字符加以区分。例如"{ TAB }"等同于按下键盘上的 Tab 键,但"TAB"则表示 TAB 三个字母。如果某个键需要使用多次,那么可以“{键名 次数}”的格式调用,例如本例中“{DOWN 4}”表示连续按下四次下箭头。如果需要向显示的对象中发送字符,需要先发送字符串再显示对象。例如要向对话框中发送密码,则必须在显示对话框之前调用 SendKeys 方法发送密码,然后显示密码框。Sub 向对话框发送字符串()Dim ans As Integer'声明变量Application.SendKeys "12345" '先发送验证码再显示对话框ans = Application.InputBox("请输入验证码", "权限", , , , , , 1)话框中MsgBox ansEnd Sub'将验证码送送到本对执行以上代码时,虽然 InputBox 方法并未设置默认值,但是在其对话框中仍然产生默认值 12345,这是过程中使用 SendKeys 方法的结果。图 11.17向对话框发送字符串Excel VBA 程序开发自学通11.1.152020-2-26第 286页 /共 508页为程指定快捷键〖案例要求〗:利用 VBA 对 VBA 过程设置快捷键。〖知识要点〗:OnKey〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Private Sub 新建工作表()Sheets.AddEnd SubSub 设定快捷键()'对程序指定一个快捷键Application.OnKey "^q", "新建工作表"End Sub第一个过程是先进设置快捷键的过程,第二个过程用于对某个过程指定快捷键。(3)光标置于过程“设定快捷键”并按下快捷键【F5】执行程序;(4)返回工作表界面,使用快捷键【Ctrl+q】呼叫“新建工作表”过程,可以发现该快捷键已经产生效用,工作表簿中可以产生一个新工作表。〖语法补充〗:(1)Application.OnKey 方法可以指定一个特定键或特定的组合键绑定到指定的过程。其语法如下:Application..OnKey(Key, Procedure)第一参数表示按键的字符串,第二参数表示要运行的过程名称的字符串。如果Procedure 为空文本 (""),则按 Key 时不发生任何操作。(2)Key 参数的可用键与表 11-7 一致,可将其中的任意键绑定到一个过程。(3)也可以使用 OnKey 将 Excel 内置的快捷键绑定到自己的程序中,例如使用快捷键【Ctrl+C】来执行过程“新建工作表”,那么可以使用以下过程:Sub 设定快捷键()'对程序指定一个快捷键Application.OnKey "^c", "新建工作表"End Sub(4)如果需取消绑定,那么可以将 OnKey 的第二参数使用空文本""。例如需要还原快捷键【Ctrl+C】给内置的复制功能,那么可以执行以下过程:Sub 恢复()Application.OnKey "^c", ""End Sub(5)OnKey 的另一个作用是屏弊 Excel 的某个内置功能。例如禁止用户使用【Ctrl+C】和【Ctrl+V】来复制、粘贴数据,那么可以使用以下过程来实现:Sub 禁止复制与粘贴()Application.OnKey "^c", "禁止"Application.OnKey "^v", "禁止"End SubSub 禁止()MsgBox "禁止使用本功能"End Sub执行过程“禁止复制与粘贴”后,快捷键【Ctrl+C】和【Ctrl+V】都已禁用,无Excel VBA 程序开发自学通2020-2-26第 287页 /共 508页法再利用它们来复制、粘贴数据。使用使用快捷键【Ctrl+C】或者【Ctrl+V】时都会弹出以下提示:图 11.18 提示当重启 Excel 后,Application.OnKey 方法设定的快捷键将会失效,所以如果需要某个指定的快捷键永远可用,可以将该过程加入 Open 事件中。(6)OnKey 必须配合其前置对象 Application 同时使用。11.1.16合并区域〖案例要求〗:将区域中将包含“丽”的单元格填充红色,'将姓“刘”的姓名所在单元格填充黄色。〖知识要点〗:Union〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 填充颜色()'将包含“丽”的单元格填充红色'将姓“刘”的姓名所在单元格填充黄色Dim rng As Range, 丽 As Range, 刘 As Range'遍历当前表已用区域For Each rng In ActiveSheet.UsedRangeIF rng Like "*丽*" Then '如果单元格中包括“丽”IF 丽 Is Nothing Then '如果变量“丽”未初始化Set 丽 = rng '将符合条件的单元格赋与变量“丽”Else '否则Set 丽 = Application.Union(丽, rng) '合并第一个找到的单元格与符合条件其它单元格End IFEnd IFIF rng Like "刘*" Then '解释同上IF 刘 Is Nothing ThenSet 刘 = rngElseSet 刘 = Application.Union(刘, rng)End IFEnd IFNext丽.Interior.ColorIndex = 3 '将符合条件的区域添加颜色刘.Interior.ColorIndex = 6End Sub以上过程中使用遍历区域的方式逐个对比单元格字符,并利用 Union 方法将所找到的符合条件的所有单元格合并成一个区域,然后对该合并区域设置背景色。Excel VBA 程序开发自学通2020-2-26第 288页 /共 508页(3)返回工作表界面,假设工作表中有图 11.19 所示数据,那么使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“填充颜色”后,程序会将找到的符合条件的单元格标示为图 11.20 所示背景色。图 11.19 成绩表图 11.20 对符合条件的姓名添加背景色〖语法补充〗:(1)Application.Union 方法返回两个或多个区域的合并区域。其语法为:Application.Union(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10,Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20, Arg21, Arg22,Arg23, Arg24, Arg25, Arg26, Arg27, Arg28, Arg29, Arg30)(2)Application.Union 方法无法合并跨表的区域。例如以下代码以一定会产生运行时错误:Sub 跨表合并()MsgBox Application.Union(Sheets(1).[a1], Sheets(2).[d1:g2]).Address(0, 0)End Sub(3)Union 可以单独使用,忽略其前置对象 Application。11.1.17获取多区域的交集〖案例要求〗:对工作表中列标题及行标题的字体加粗,再将标题以外的所有数据倾斜显示。其中标题的行数则用户手工指定。〖知识要点〗:Intersect 与 InputBox〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 加粗与倾斜()Dim 行标题 As Byte, 列标题 As Byte'错误时继续执行On Error Resume NextStar:'设置一个标签'用户指定行、列标题数量行标题 = Application.InputBox("请输入行标题数", "标题行数", 2, , , , , 1)列标题 = Application.InputBox("请输入列标题数", "标题列数", 1, , , , , 1)'如果存在错误则返回重新输入IF Err 0 Then MsgBox "只能输入 1 到 255 之间的值": Err.Clear: GoTo Star'如果行标题大于总行数或者列标题大于总列数IF 行 标 题> ActiveSheet.UsedRange.Rows.Count Or 列 标 题>=Excel VBA 程序开发自学通2020-2-26第 289页 /共 508页ActiveSheet.UsedRange.Columns.Count Then'提示并返回,重新输入MsgBox "不能超过已用区域": GoTo StarEnd IFDim rng As Range'利用变量简化输入Set rng = ActiveSheet.UsedRange'对已用区域加粗rng.Font.Bold = TrueWith Application.Intersect(rng.Offset(行标题, 列标题), rng).Font.Bold = False'取消加粗.Italic = True'倾斜End WithEnd Sub以上过程中首先让用户输入标题的行数、列数,考虑到用户输入值超过 Byte 范围的潜在错误,通过防错机制捕到错误时返回 Star 标签让用户重新录入,直到数值处于可接受的范围为止。(3)返回工作表界面,假设工作表中有图 11.21 所示数据,那么使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“加粗与倾斜”后,分别弹出两次对话框,让用户指定行标题与列标题。本例中使用默认值即可。设置 OK 后其结果将如图 11.22所示:图 11.21 成绩表图 11.22 加粗标题、倾斜成绩区域〖语法补充〗:(1)Application.Intersec 方法返回一个 Range 对象,该对象表示两个或多个区域重叠的矩形区域。它的语法如下:Application.Intersect(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10,Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20, Arg21, Arg22,Arg23, Arg24, Arg25, Arg26, Arg27, Arg28, Arg29, Arg30)(2)Application.Interse 方法返回多区域的重叠区域,如果其参数不存在重叠部分则返回 Nothing.(3)Intersec 方法可以单独使用,忽略其前置对象 Application。11.1.18中断程序到一定时间后再继续〖案例要求〗:某工作表数据在 10 分钟内一定会更新一次,现利用 VBA 完成每10 分钟打印一次工作表数据,从而实现打印的资料自动更新。直到晚上 20:00 时停止打印。Excel VBA 程序开发自学通2020-2-26第 290页 /共 508页〖知识要点〗:Wait〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 每 10 分钟打印工作表()Dim item As Integer'先打印一次工作表ActiveSheet.UsedRange.PrintOut'只要时间小于 20:00 就循环执行Do While TimeValue(Now) < TimeValue("20:00:00")'程序中断 10 分钟Application.Wait (Now + TimeValue("00:10:00"))'记录打印次数item = item + 1'打印当前工作表ActiveSheet.UsedRange.PrintOutLoopMsgBox "已打印" & item & "次"End Sub本过程中利用 TimeValue 函数获取现在的时间值,再与指定的时间进行比较,如果小于晚上 20 点则循环执行过程,打印当前表数据。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“每10 分钟打印工作表”,程序会马上打印一次当前表,然后每隔 10 分钟再打印一次,直到晚上 20 点。〖语法补充〗:(1)Application.Wait 方法可以暂停运行宏,直到特定时间才可继续执行。它的语法如下:Application.Wait (Time)其中参数 Time 表示望宏继续执行的时间。(2)Wait 方法可以暂停 Excel 的所有操作,并且在 Wait 阶断可能禁止用户对计算机做其他操作。换一种说法,Wait 方法在暂停时间会占用大量的内存,让电脑处于无响应状态。它虽然在使用上比 VB 中的时间控件更具方便性,但这个特点限制了它的应用范围。(3)Wait 的参数即可以使用一个指定的时间,也可以是现在之后的某个时间。例如暂停到 14 点钟再执行,以及从现在开始暂停 14 分钟,两个代码分别如下:Application.Wait "14:00:00"Application.Wait Now() + TimeValue("00:14:00")(4)Wait 不可以单独使用,必须配合前置对象 Application。11.1.19调用内置对话框〖案例要求〗:打开设置打印标题对话框。〖知识要点〗:Dialogs〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Excel VBA 程序开发自学通2020-2-26第 291页 /共 508页Sub 打开设置打印标题对话框()Application.Dialogs(23).ShowEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开设置打印标题对话框”,将会弹出图 11.23 所示对话框。图 11.23 打开内置对话框〖语法补充〗:(1)Dialog 对象代表内置的 Excel 对话框,利用 Show 方法可以显示绝大部分Excel 的对话框。它的基本语法如下:Application.Dialogs(Index).Show其参数 Index 用于指定对话框类型,可以使用内置的常量,也可以使用数值。由于该量很多,限于篇幅所限,读得可以到随书光盘中查看。(2)虽然通过菜单、功能区也可以打开 Excel 的对话框,但使用 VBA 方法可以将部分综合型对话框中某个选项抽离出来,或者将某一类型的选项集中一个对话框,对工作也是具有实用性的。例如编号为 27 和 52 的“显示选项”对话框和“清除”对话框,见图 11.24 和图 11.25 所示:图 11.24 显示选项图 11.25 清除(3)如果用户需要逐个显示所有 Dialogs 支持的所有对话框,可以使用以下过程:Sub 显示内置对话框()On Error Resume NextDim Count As Integer, Item As IntegerFor Item = 1 To Application.Dialogs.CountApplication.Dialogs(Item).ShowNextExcel VBA 程序开发自学通2020-2-26第 292页 /共 508页End Sub11.1.20滚动显示 Excel 状态栏信息〖案例要求〗:在状态栏滚动显示几句歌词。〖知识要点〗:StatusBar〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Dim Bl As BooleanSub 诗词滚动()Dim Tim As IntegerBl = True '将变量设置为 true'待显示的诗歌Application.StatusBar = "相见时难别亦难 东风无力百花残 春蚕到死丝方尽 蜡炬成灰泪始干"DoFor Tim = 1 To 2500'这个数字根据需要修改DoEvents'转移控制权,从而突出动画效果Next Tim'交换文字位置,形成动画Application.StatusBar = Mid(Application.StatusBar, 2, Len(Application.StatusBar)- 1) & Left(Application.StatusBar, 1)Loop Until Bl = False '直到 Bl 变量成为 False 时停止循环Application.StatusBar = ""End SubSub 停()Bl = False '将变量设为 FalseEnd Sub其中“Dim Bl As Boolean”声明公共变量必须放在模块的顶部,不能在两个过程之间。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“诗词滚动”,在状态栏立即出现一段歌词在滚动。根据大家的硬件不同,其滚动的速度也有差异。〖语法补充〗:(1)Application.StatusBar 属性可返回或设置状态栏中的文字。可以对它直接赋值,但因状态栏空间有限,汉字字符不宜超过 30 个。(2)状态栏信息在重启 Excel 时会重置,如果需要永远显示这个滚动信息,可以将它加入工作簿及 Open 事件中。11.1.21添加自定义序列〖案例要求〗:将加学位序列,使工事表中的应征者可以按学历排序。〖知识要点〗:AddCustomList〖实现步骤〗:(1)单击菜单【插入】\【模块】;Excel VBA 程序开发自学通2020-2-26第 293页 /共 508页(2)在模块代码窗口输入以下代码:Sub 添加序列()'将数组中添加到序列中Application.AddCustomList Array("小学", "初中", "高中", "大学", "博士")End Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加序列”,在“自定义序列”对话框中可以看到刚才所添加的序列。图 11.26 添加新的序列(4)如果需要对图 11.27 所示的人事资料表以 B 列为基准对所有人员按学历升序排序,那么可以在排序时在“排序”对话框的“次序”下拉列表中选择“自定义序列”的方式按学历排序。排序后结果如图 11.28 所示:图 11.27 人事资料表图 11.28 排学历排序的结果(5)如果不手工排序,而是通过 VBA 完成排序,那么 Excel 2007 可以不借助自定义序列,而使用 Sort 方法独立完成,而 Excel 2003 则必须使用排序功能配合自定义序列功能完成。Excel 2007 对图 11.27 中进行按学历排序的过程如下:Sub 自定义排序 1() '2007 专用'设置排序状态及条件区域ActiveSheet.Sort.SortFields.Add Key:=Range("B2:B10"), SortOn:=xlSortOnValues, _Excel VBA 程序开发自学通2020-2-26第 294页 /共 508页Order:=xlAscending, CustomOrder:="小学,初中,高中,大学,博士"'对 A2:B1 区域按 B2:B10 进行自定义排序With ActiveSheet.Sort.SetRange Range("A2:B10") '实际排序区域.Apply ' 应用排序格式End WithEnd Sub从以上代码可以看到,Excel 2007 独有的 Sort 对象在 Excel 2003 的 Sort 方法上改进了许多,它自带自定义序列的参数,脱离“自定义序列”功能而单独完成。Sub 自定义排序 2() '2003 与 2007 通用Range("A1:B10").Sort Key1:=Range("B2"), Order1:=xlAscending, Header:= _xlYes, OrderCustom:=13, MatchCase:=False, Orientation:=xlTopToBottom, _SortMethod:=xlPinYin, DataOption1:=xlSortNormalEnd Sub本过程为 2003 中使用的代码,为了体现兼容性,Excel 2007 中使用此过程也可以完成按学历排序。但是微软显然意识到了这种方式的缺陷——OrderCustom 只能以相对位置指定自定义序列,当对序列进行了编辑或者增删后容易产生错误——才在Excel 2007 中做了改进,读者也应该与时俱进,使用更先进的排序方式。〖语法补充〗:(1)Application.AddCustomList 方法可以为自定义自动填充或自定义排序添加自定义列表。它的具体语法如下:Application.AddCustomList(ListArray, ByRow)其第一参数用于指定字符串数组或 Range 对象为序列的数据源;第二参数则仅当ListArray 为 Range 对象时使用。如果为 True,则使用区域中的每一行创建自定义列表;如果为 False,则使用区域中的每一列创建自定义列表。(2)如果需要一组不确定的数据添加到自定义序列,或者该组数据非常大,通常从单元格导入数据,而非直接使用数组。例如需要将 A、B、C……Z 等等字母添加到自定义序列,那么可以用单元格做辅助区,代码如下:Sub 定义字母序列()Dim i As Integer'防错,否则已经有该序列时将出错On Error Resume Next'关闭屏幕刷新,加愉速度Application.ScreenUpdating = FalseFor i = 1 To 26 '遍历 26 个字母'利用 Chr 函数产生字符码,代码 65 即可大写字母 A,66 即为 B,以此类推Cells(Rows.Count, i) = Chr(64 + i)Next i'将辅助区导入序列Application.AddCustomList Cells(Rows.Count, 1).Resize(1, 26)'删除辅助区Cells(Rows.Count, 1).Resize(1, 26).ClearApplication.ScreenUpdating = TrueEnd Sub11.1.22添加名称〖案例要求〗:生成包括一周中每天的名称,方便单元格输入。Excel VBA 程序开发自学通2020-2-26第 295页 /共 508页〖知识要点〗:Names〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 生成日期名称()'添加一个名称,包括一个星期每一天Application.Names.Add Name:="星期", RefersToR1C1:="={""星期一"",""星期二"",""星期三"",""星期四"",""星期五"",""星期六"",""星期日""}"End Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成日期名称”,将会产生一个工簿级别的名称;(4)选择 A1 单元格,并输入公式“=INDEX(星期,ROW(A1))”,将公式向下填充至 A7,直充结果如图 11.29 所示,这是常量名称的应用;(5)也可以利用区域数组公式一次性输入到区域中。鉴于代码中构造数组时使用了逗号,表示横向数组,那么输入公式需要进行转置。完整公式为“=TRANSPOSE(星期)”,效果见图 11.30 所示:图 11.29 逐个取出名称中的元素图 11.30 一次性取出数组所有元素〖语法补充〗:(1)Names.Add 方法可以定义新名称。其语法如下:表 达 式 .Names.Add(Name, RefersTo, Visible, MacroType, ShortcutKey, Category,NameLocal, RefersToLocal, CategoryLocal, RefersToR1C1, RefersToR1C1Local)其 中 表 达 式 可 以 是 Application 、 Workbook 和 Worksheet 等 , 而Application.Names.Add 其实等效于 ActiveWorkbook.Names.Add。其中最常用的是前两个参数,用于指定名称的引用地址和名字。(2 )Names 有工作表级别和工作簿级别之分,而 Application.Names.Add 和ActiveWorkbook.Names.Add 方法都添加的工作簿级别名称;Worksheet.Names.Add 方式添加的是工作表级别名称,只能指定的工作表才可以使用该名称。11.1.23将自定义数标记为易失性函数。〖案例要求〗:开发一个自定义函数,获取选区的地址。有相对引用、绝对引用和混合引用可选,当单元格插入或者删除时要及时更新公式的值。〖知识要点〗:Volatile、ThisCell〖实现步骤〗:(1)单击菜单【插入】\【模块】;Excel VBA 程序开发自学通2020-2-26第 296页 /共 508页(2)在模块代码窗口输入以下代码:Function Address2(style As Byte, Optional rng As Range) '声明函数名称,第二个为可选参数Application.Volatile'声明为易失性函数'如果省略第二参数则将公式所在单元格赋与变量IF rng Is Nothing Then Set rng = Application.ThisCell'根据第一参数决定单元格地址的引用形式,包括相对引用、绝对引用、混合引用Select Case styleCase 1Address2 = rng.AddressCase 2Address2 = rng.Address(0, 0)Case 3Address2 = rng.Address(1, 0)Case ElseAddress2 = rng.Address(0, 1)End SelectEnd Function该函数有两个参数,第一参数用于限制地址。当第一参数是 1 时产生绝对引用地址,为 2 时产生相对引用地址,为 3 时产生列相对、行绝对地址,为 4 时产生列绝对、行相对地址。(3)返回工作表界面,在单元格输入公式可以产生对应的单元格地址。例如:=Address2(1)——如果在 A1 输入公式则结果为$A$1,忽略第二参数则引用公式单元格地址=Address2(2,C2:D3)——结果等于 C2:D3,相对引用=Address2(3,6:7)——结果等于$6:$7,列相对、列绝对引用=Address2(4,D:N)——结果等于$D$4,绝对引用如果将公式引用的单元格移动位置,那么公式可以立新更新结果。〖语法补充〗:(1)Application.Volatile 方法用于将用户自定义函数标记为易失性函数,无论何时在工作表的任意单元格中进行计算时,易失性函数都必须重新进行计算。(2)Application.ThisCell 属性返回一个单元格,用户定义的函数将作为 Range对象从这里调用。更通俗地说法即 ThisCel 代表公式所在单元格。11.1.24选定任意工作簿中的任意区域。〖案例要求〗:选择第三个工作表的已用区域〖知识要点〗:Goto〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 选择其它表已用区域()Application.Goto Reference:=Worksheets("Names").UsedRange, scroll:=TrueEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“选择其它表已用区域”,程序将选择名为“Names”的工作表的已用区域。〖语法补充〗:Excel VBA 程序开发自学通2020-2-26第 297页 /共 508页(1)Application.Goto 方法可以选定任意工作簿中的任意区域,并且如果该工作簿未处于活动状态,就激活该工作簿。它的语法如下:Application.Goto(Reference, Scroll)其中第一参数表示待选定的目标,如果省略该参数,目标将为最近一次用 Goto方法选定的区域。第二参数如果为 True,则滚动窗口直至区域的左上角出现在窗口的左上角中。如果为 False,则不滚动窗口。(2)Goto 方法与 Range 的 Select 方法有以下区别:表 11-8Goto方法与Select方法的区别比较项目如果指定的区域不在位于最前面屏幕的工作表中,选定该区域之前切换至该工作表具有让用户滚动目标窗口的 Scroll 参数GOTOSELECT是否是否前一次选定区域被增加到以前选定区域的数组(PreviousSelections)中是否具有 Replace 参数跨表或跨工作簿时可以一步完成否是是否(3)如果本例中的任何交 Select 来完行,那么必使用两步,先激活工作表,再选择单元格。完整代码如下:Sub 选择其它表已用区域()Worksheets("Names").SelectWorksheets("Names").UsedRange.SelectEnd Sub11.1.25设置应用程序的可见性。〖案例要求〗:为 Excel 设定登录权限,在录入用户名时隐藏应用程序,正确后再恢复〖知识要点〗:Visible〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 验证密码()'隐藏 Excel 应用程序Application.Visible = FalseDim Users As String'让用户确认自己的权限Users = Application.InputBox("请输入密码", "权限确认", , , , , , 3)Application.ScreenUpdating = False'如果不是录入 Admin 则退出程序IF Users "Admin" ThenApplication.QuitEnd IF'恢复 Excel 程序的可见性Application.Visible = TrueApplication.ScreenUpdating = TrueEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“验Excel VBA 程序开发自学通2020-2-26第 298页 /共 508页证密码”,将弹出图 11.31 所示权限验证对话框,同时隐藏 Excel 工作簿的主体界面。如果输入任何不等于“Admin”的字符串,Excel 将关闭,否则返回正常页面。图 11.31 让用户录入密码实际工作中需要将过程改成工作簿 Open 事件更好,让其自启动。〖语法补充〗:(1)Application.Visible 属性用于返回或设置一个 Boolean 值,它确定 Excel 应用程序对象是否可见。(2)在隐藏主体界面后,必须在适当的时候恢复,否则无法继续操作 Excel.11.1.26设置批注的显示方式〖案例要求〗:弹出对话框,根据用户的录入数值设置批注显示方式〖知识要点〗:DisplayCommentIndicator〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 设置批注显示方式()Dim Style As ByteOn Error Resume Next '防错Star:'设置一个标签'让用户选择批注显方式Style = Application.InputBox("请确认批注显示方式:" & Chr(10) & "输入 1:显示批注与红色箭头" & Chr(10) _& "输入 2:仅仅显示红色箭头" & Chr(10) & "输入 3:隐藏批注与箭头", "显示方式",2, , , , , 1)'如果有错误(超过 Byte 的允许范围),那么返回 Star 标签继续执行IF Err.Number > 0 Then Err.Clear: GoTo Star'如果在 1 到 3 范围之外则返回 Star 标签继续执行IF Style 3 Then MsgBox "只能是 1、2 或者 3", 64, "提示": GoTo StarSelect Case Style '根据用户的录入数值设置批注的显示方式Case 1Application.DisplayCommentIndicator = xlCommentAndIndicator '显示全部Case 2Application.DisplayCommentIndicator = xlCommentIndicatorOnly '仅显示箭头Case 3Application.DisplayCommentIndicator = xlNoIndicator '全部隐藏End SelectEnd Sub以上过程中,利用了防错机制处理不规范的录入值。同时对于不在允许范围 1、2和 3 之内的输入值,也会返回标签 Star 处继续等待用户录入新的值,一直循环到出现规范数值为止。Excel VBA 程序开发自学通2020-2-26第 299页 /共 508页(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“设置批注显示方式”,将弹出“显示方式”对话框,有三个设置选项,默认值为 2,见图11.32 所示。如果输入负数或者大于 256 的任何数值都会继续弹出对话框等待用户录入正确值;如果用户输入 4,则会弹出图 11.33 所示的提示,然后再返回对话框等待用户录入正确值;图 11.32 让用户输入决定显示方式的值图 11.33 输入 4 时弹出的提示(4)如果分别输入 1、2 或者 3,那么其结果分别如图 11.34、图 11.35 和图 11.36所示:图 11.34 显示批注与箭头图 11.35 显示箭头图 11.36 全部隐藏〖语法补充〗:(1)Application.DisplayCommentIndicator 属性可返回或设置单元格显示批注和标识符的方式。可以是 XlCommentDisplayMode 常量之一。XlCommentDisplayMode常量包括以下三个可选项:表 11-9 显示批注的三种常量名称xlCommentAndIndicator值1xlCommentIndicatorOnly-1xlNoIndicator0描述任何时候都显示批注和标识符只显示标识符。鼠标指针在单元格上移动时显示批注任何时候都不显示批注也不显示标识符(2)本例三个方式的设置对当前工作簿中所有工作表都生效。(3)DisplayCommentIndicator 不能单独使用,需要配合前置对象 Application。11.2 Range 对象应用案例Range 对象是数据最基本的载体,制表中使用最频繁的自然就是单元格对象——Range。本节对 Range 对象进行实例演示。11.2.1清除单元格格式〖案例要求〗:图 11.37 所示单元格的显示值并非单元格的实际值,解决这个问题即为对表格清除格式设置。现要求除区域中数值单元格的格式。Excel VBA 程序开发自学通2020-2-26第 300页 /共 508页〖知识要点〗:ClearFormats、SpecialCells〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 清除数值区格式()Range([a1],Cells(Rows.Count,1).ClearFormatsEnd Sub4).End(xlUp)).SpecialCells(xlCellTypeConstants,以上过程中使用“End(xlUp)”主要为了提升程序的通用性,当工作表中删除或者增加数据时程序可以不用修改代码也可以正确操作所有数据。而使用“SpecialCells”方法则是为了定位数值、忽略其它数据。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“清除数值区格式”,程序执行完毕后,所有数字单元格恢复其原来的值,见图 11.38 所示:图 11.37 成绩表图 11.38 恢复成绩表原始数据〖语法补充〗:(1)Range.SpecialCells 方法用于返回一个 Range 对象,该对象代表与指定类型和值匹配的所有单元格。类似于菜单中的“定位”工具。SpecialCells 有以下参数可选,指定单元格类型:表 11-10 SpecialCells参数列表常量xlCellTypeAllFormatConditionsxlCellTypeAllValidationxlCellTypeBlanksxlCellTypeCommentsxlCellTypeConstantsxlCellTypeFormulasxlCellTypeLastCellxlCellTypeSameFormatConditionsxlCellTypeSameValidationxlCellTypeVisible含义任意格式单元格含有验证条件的单元格空单元格含有注释的单元格含有常量的单元格含有公式的单元格已用区域中的最后一个单元格含有相同格式的单元格含有相同验证条件的单元格所有可见单元格值-4172-41744-41442-412311-4173-417512如果使用了 XlSpecialCellsValue 参数,那么它还可以使用以下常量,用于指定值的类型:表 11-11 XlSpecialCellsValue常量表常量xlErrors值16Excel VBA 程序开发自学通2020-2-26xlLogicalxlNumbersxlTextValues第 301页 /共 508页412在本例中 SpecialCells(xlCellTypeConstants, 1)表示定位于常量中的数值。(2)Range.ClearFormats 方法用于清除对象的格式设置。格式设置包括颜色、字体名称、字体大小、边框、数字格式、对齐方式等等属性,但不包括单元格中的数据。如果需要删除数据保留格式,那么可用以下两种方式:Range.ClearContents——清除单元格内容Range= "" ——赋空值本节关于 Range 对象的所有实例代码参见光盘:..\ 第十一章\Range.xlsm11.2.2复制单元格数据〖案例要求〗:将指定工作表中已用区域的数据复制到当前表,如果当前表空白,则在 A1 开始放置数据,否则放置在已用区域的下一行开始放置数据。〖知识要点〗:Copy、PasteSpecial〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 将其它工作表的已用区域复制到本工作表()Dim rng As Range'如果 A 列空白IF WorksheetFunction.CountA([a:a]) = 0 ThenSet rng = [a1] '将 A1 赋与变量 RngElse'否则将第一个空单元格赋与变量 RngSet rng = Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)End IF'将第一个工作表中的单元格复制到 Rng(如果复制的数据多于一个单元格,则以 Rng 为基准向右、向下延伸)Sheets("ClearFormats").UsedRange.Copy rngEnd Sub以上过程首先判断当前表 A 列是否空白,如果空白则将数据复制到 A1,否则复制到已用区域的下一个空行。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将其它工作表的已用区域复制到本工作表”,工作表“ClearFormats”的所有数据都会复制到当前表,包括格式信息。〖语法补充〗:(1)Range.Copy 方法用于将单元格区域复制到指定的区域或剪贴板中。它的语法如下:Range.Copy(Destination)参数 Destination 用于指定数据要复制到的新区域。如果省略此参数, Excel 会将数据复制到剪贴板。例如将 A1 的值复制到 B3,那么使用以下语句:[a1].Copy [b3]如果将 A1 的值复制到剪贴版供后续调用,那么可以使用以下语速句:[a1].CopyExcel VBA 程序开发自学通2020-2-26第 302页 /共 508页(2)Range.Copy 方法总是带有区域中的格式信息,而不是仅仅复制数据。(3)复制一个区域时,Copy 的第二参数只需要指定一个单元格,粘贴数据时将以该单元格为基准向右、向下扩展到与原数据区域中同大小的区域。如果数据源区域是整行或者整列,那么这个目标单元格必须位于工作表的左端或者顶端,否则因空间不足而产生运行时错误。(4)如果需要复制区域中的某项信息,例如数值、格式或者数据有效性等等,那么应该使用选择性粘贴。选择性粘贴必须通过两步才能完成,即复制数据到剪贴版,最后粘贴数值或者格式信息。如果本例中复制区域改为复制数值,那么代码可以修改为:Sub 复制数值()'复制目标数据Sheets("ClearFormats").UsedRange.CopyDim rng As RangeIF WorksheetFunction.CountA([a:a]) = 0 ThenSet rng = [a1]ElseSet rng = Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)End IF'在当前表 A 列第一个非空单元格选择性粘贴值rng.PasteSpecial Paste:=xlPasteValues, Operation:=xlNoneEnd Sub(5)Range.PasteSpecial 方法用于将 Range 从剪贴板粘贴到指定的区域中。它的语法如下:Range.PasteSpecial(Paste, Operation, SkipBlanks, Transpose)其中四个参数的含义见表 11-12:表 11-12 Range.PasteSpecial参数详解名称PasteOperation数据类型XlPasteTypeXlPasteSpecialOperationSkipBlanksVariantTransposeVariant描述要粘贴的区域部分粘贴操作如果为 True,则不将剪贴板上区域中的空白单元格粘贴到目标区域如果为 True,则在粘贴区域时转置行和列。默认值为False其中第一参数数据类型 XlPasteType 有 11 个可选项,包括值、格式、公式、有效性等等。详见表 11-14 所示:表 11-13 选择性站贴的可选数据类型名称xlPasteValuesxlPasteCommentsxlPasteFormulasxlPasteFormatsxlPasteAllxlPasteValidationxlPasteAllExceptBordersxlPasteColumnWidthsxlPasteFormulasAndNumberFormats值-4163-4144-4123-4122-410467811描述粘贴值粘贴批注粘贴公式粘贴复制的源格式粘贴全部内容粘贴有效性粘贴除边框外的全部内容粘贴复制的列宽粘贴公式和数字格式Excel VBA 程序开发自学通2020-2-26xlPasteValuesAndNumberFormatsxlPasteAllUsingSourceTheme第 303页 /共 508页1213粘贴值和数字格式使用源主题粘贴全部内容根据上表,如果仅仅复制格式,那么复制 A1 的有效性设置与列宽到 C1,即将C1 单元格的宽度与 A1 一致,且让其数据有效性设置也一致,但不改变 C1 的原有数据,那么可以采用以下过程:Sub 复制 A1 的有效性设置与列宽()[A1].Copy[C1].PasteSpecial Paste:=8[C1].PasteSpecial Paste:=6End Sub11.2.3将区域中的数据合并到一个单元格中〖案例要求〗:将区个区域合并居中,同时保留合并前的所有数据。〖知识要点〗:Merge、UnMerge、HorizontalAlignment、VerticalAlignment〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 合并指定区域的值()Dim rng As Range, str As StringWith Range("A1:C1")'如果区域中有合并单元格,则先取消其合并.UnMergeFor Each rng In Range("A1:C1")'遍历区域,合并所有数据str = str & rng.TextNext rng'关闭警告窗口Application.DisplayAlerts = False.Merge '合并区域Application.DisplayAlerts = True'居中对齐,并将合并后的值赋与合并区域.HorizontalAlignment = xlCenter.VerticalAlignment = xlCenter.Value = strEnd WithEnd Sub以上过程将指定区域取消合并(如果存在合并区域的话),然后连接区域中的所有数据,再将区域合并居中,最后将连接的字符串赋与合并区域。合并区域时会提示(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并指定区域的值”。如果工作表中 A1:C1 有图 11.39 所示的数据,那么执行过程后将产生图 11.40 所示效果——合并单元格的同时将数据也合并。Excel VBA 程序开发自学通2020-2-26第 304页 /共 508页图 11.39 合并前的日期图 11.40 合并后的日期〖语法补充〗:(1)Range.Merge 方法由指定的 Range 对象创建合并单元格。其语法如下:Range.Merge(Across)其中参数 Across 如果为 True,则将指定区域中每一行的单元格合并为一个单独的合并单元格。默认值是 False。例如将 A1:D4 合并为 4 个合并区域,每行一个区域,那么可以使用以下代码,对应于 Excel 2007 功能区【开始】\【合并后居中】\【跨越合并】,执行结果见图 11.41 所示:Range("A1:D4").Merge (True)图 11.41 利用代码实现跨越合并(2)Range.UnMerge 方法用于将合并区域分解为独立的单元格。在取消合并区域后,仅仅该区域左上角单元格有数据,其它单元格保持空白。(3)Range.HorizontalAlignment 属性和 VerticalAlignment 属性代表单元格的对齐方式,包括以下五种方式: xlCenter xlDistributed xlJustify xlLeft xlRight11.2.4多工作表数据合并且添加边框〖案例要求〗:合并某班三个年级的成绩,存放在当前表 D1。〖知识要点〗:Consolidate、Borders〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 合并计算()'将多工作表的成绩数据合并,保存区域与合并区域大小一致,但其左上角单元格是 D1[D1].Consolidate Sources:=Array(" 一 班 !R1C1:R8C2", " 二 班 !R1C1:R8C2", " 三班!R1C1:R8C2"), _Excel VBA 程序开发自学通2020-2-26第 305页 /共 508页Function:=xlSum, TopRow:=True, LeftColumn:=True, CreateLinks:=False'对合并成绩区域添加边框[D1].CurrentRegion.Borders.LineStyle = xlContinuousEnd Sub以上过程将三个工作表的相同大小的数据区域进行合并计算,然后添加边框。汇总方式是求和。根据需求,也可以对三个年级的成绩进行平均、计数等等计算。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并计算”,三个班级的成绩将合并到当前表中,存放区域是 D1 为基准,扩展到合并前区域的相同大小。图 11.42 中包括某班三个年级的数据及合并后的汇总值。图 11.42 合并三个工作表的数据〖语法补充〗:(1)Range.Consolidate 方法可以将多个工作表中多个区域的数据合并计算至单个工作表上的单个区域中。它的具体语法如下:Range..Consolidate(Sources, Function, TopRow, LeftColumn, CreateLinks)五个参数的含义如下:表 11-14 Range.Consolidate方法参数详解名称SourcesFunctionTopRowLeftColumnCreateLinks描述以文本引用字符串数组的形式给出合并计算的源,该数组采用 R1C1-样式表示法。这些引用必须包含将要合并计算的工作表的完整路径XlConsolidationFunction 常量之一,用于指定合并计算的类型。如果为 True,则基于合并计算区域中首行内的列标题对数据进行合并。如果为False,则按位置进行合并计算。默认值为 False如果为 True 则基于合并计算区域中左列内的行标题对数据进行合并计算。如果为False,则按位置进行合并计算。默认值为 False如果为 True,则让合并计算使用工作表链接。如果为 False,则让合并计算复制数据(2)其中第二参数表示汇总方式,包括以下几个可选常量:表 11-15 汇总方式详解名称xlAveragexlCountxlCountNumsxlMaxxlMinxlProduct值-4106-4112-4113-4136-4139-4149描述平均计数只计数数值最大值最小值乘Excel VBA 程序开发自学通2020-2-26xlStDev-4155xlStDevP-4156xlSum-4157xlUnknown1000xlVarxlVarP-4164-4165第 306页 /共 508页基于样本的标准偏差基于全体数据的标准偏差总计未指定任何分类汇总函数基于样本的方差基于全体数据的方差(3)合并计算是一项非常有用的工具,但属于 Excel 默认功能以外的加载宏工具,需要安装才可以使用。(4)Range.Borders 对象由四个 Border 对象组成的集合,它们代表单元格的四个边框。利用其 LineStyle 属性可以对单元格设置四个方向的边框线。表 11-16 包括所有可选的线型:表 11-16 边框边线列表名称xlContinuousxlDashxlDashDotxlDashDotDotxlDotxlDoublexlLineStyleNonexlSlantDashDot11.2.5值1-411545-4118-4119-414213描述实线虚线点划相间线划线后跟两个点点式线双线无线条倾斜的划线让高度与宽度自动适应数据〖案例要求〗:对当前工作簿中所有工作表进行调整,让其根据单元格的字符敲开整行高与列宽。〖知识要点〗:AutoFit〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 自动适应()Dim sht As Worksheet'遍历所有工作表For Each sht In Sheets'让已用区域的行高与列宽都自动适应文字sht.UsedRange.Rows.AutoFitsht.UsedRange.Columns.AutoFitNext shtEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“自动适应”,所有工作表中的单元格都将瞬间调整完成。〖语法补充〗:(1)Range.AutoFit 方法用于更改区域中的列宽或行高以达到最佳匹配。Range对象必须是行或行区域,或者列与列区域。否则该方法将产生错误。本例中采用了Excel VBA 程序开发自学通2020-2-26第 307页 /共 508页Rows 和 Columns 来将已用区域转换成行区域与列区域。(2)如果需要将单元格 B2 的当前区域调整为自动行高,可以使用以下代码:Range("B2").CurrentRegion.EntireRow.AutoFit(3)如果需要在输入字符时就自动整列,而不是手工执行程序,那么可以将代码置于工作表的 Change 事件代码。11.2.6在区域中精确查找〖案例要求〗:在当前工作表中精确查找名字为两个,且第一个字是“天”的公司。〖知识要点〗:Find〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 精确查找()Dim 目标 As Range, rng As Range, firstAddress As String'对当前表已用区域中查找With ActiveSheet.UsedRange'指定查找方式为按值找查,对象是“天?公司”,?表示占位符'LookAt:=xlPart 表示模糊查找,LookAt:=xlWhole 表示精确查找Set 目标 = .Find("天?公司", LookIn:=xlValues, LookAt:=xlWhole)IF Not 目标 Is Nothing Then'如果找到firstAddress = 目标.Address '记录其记录Do'开始循环,查找下一个'此 IF 语句用于将查到的所有目标合并成一个区域IF rng Is Nothing ThenSet rng = 目标ElseSet rng = Union(目标, rng)End IFSet 目标 = .FindNext(目标)'查找下一个'循环查找,只要下一次查到单元格地址不等于第一次查到目标的单元格就继续查找Loop While Not 目标 Is Nothing And 目标.Address firstAddressEnd IFEnd Withrng.Select'选择所有找到的结果End Sub该过程在当前表已用区域中查找“天?公司”,并将找到的所有单元格合并成一个区域,最后选择该区域。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“精确查找”,程序会瞬间选择所有符合条件的单元格,见图 11.43 所示(为了更清楚的看到选择的对象,特将选区填充不同的背景色加以区分);(4)如果使用模糊查找,找到所有包括“天”字的公司,那么代码中“天?公司”需要修改成“*天*”,其查找结果见图 11.44 所示:Excel VBA 程序开发自学通2020-2-26图 11.43 查找“天?公司”第 308页 /共 508页图 11.44 查找“*天*”公司〖语法补充〗:(1)Range.Find 方法用于在区域中查找特定信息,包括精确查找与模糊查找。它的语法如下:Range.Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase,MatchByte, SearchFormat)各参数含义如下:表 11-17 Range.Find方法各参数含义列表名称WhatAfterLookInLookAtSearchOrderSearchDirectionMatchCaseMatchByteSearchFormat描述要搜索的数据,可为字符串或任意 Excel 数据类型表示搜索过程,将从其之后开始进行的单元格此单元格对应于从用户界面搜索时的活动单元格的位置信息类型可为以下 XlLookAt 常量之一:xlWhole 或 xlPart可为以下 XlSearchOrder 常量之一:xlByRows 或 xlByColumns搜索的方向如果为 True,则搜索区分大小写默认值为 False只在已经选择或安装了双字节语言支持时适用如果为 True,则双字节字符只与双字节字符匹配如果为 False,则双字节字符可与其对等的单字节字符匹配搜索的格式其中有几个参数较为重要:第一参数 What:它表示要查找的对象,支持通配符“?”和“*”。同时因为支持通配符,使其具有了控制精确查找与模糊查找的功能。第三参数 LookIn:它用于指定查找方式,包括按值查找、按公式查找及按批注查找,在本例中是按值查找。按值查找与按公式查找的区别是:按公式查找时,是在单元格显示在编辑栏中的字符中查找,而按值查找时,则在单元格的显示字符中查找,这两者有较大的区别。例如某单元格中的公式是“=LEFT("中国电信",3)”,如果按值查找“中国电信”,那么它不符合条件,如果按公式查找则符合条件。第四参数 LookAt:它可以指定是精确查找或者模糊查找,它值为 xlWhole 时表示精确查找,值为 xlPart 时为模糊查找。所谓精确查找,是指单元格整体符合条件;而模糊查找则表示单元格部分字符符合件即可。例如单元格中字符串为“中国电信”,那么查找“中国”时,只有模糊查找时它才符合条件。但是因为第一参数支持通配符,那么将 LookAt 设置为 xlWhole 时仍然可以实现糊模查找。例如第一参数为“*中国*”,那么不管 LookAt 参数如何设置都按模糊查找处理。第七参数 MatchCase:它表示查找英文时是否区分大小写,设为 True 时区分大小Excel VBA 程序开发自学通2020-2-26第 309页 /共 508页写,设置为 False 时不区公大小写。(2)Find 方法默认返回一个 Range 对象,它代表第一个在其中找到该信息的单元格。不管有多少符合条件的单元格,只返回一个单元格。如果要查找其它值,需要配合 FindNext 继续查找下一个。(3)Find 方法不影响选定区域或当前活动的单元格,它仅仅将符合条件的单元格返回给 VBA 过程,但不选择或者激活目标单元格,程序员需要在代码中加入 Select才可以选择目标。(4)每次使用此方法后,参数 LookIn、LookAt、SearchOrder 和 MatchByte 的设置都将被保存。如果用户未在代码中注明这四个参数,那么则调用上次保存的值。所以为了不产生意外,程序员应该养成注明参数的习惯,特别是 LookIn 和 LookAt 两个重点参数。(5)Find 查找支持通配符,具有很强的查找能力。但是不支持比较运算符,例如查找小于 20 的值,或者 500 到 100 之间的值。如果有这种需求,需要借用 For 循环来完成。11.2.7替换不规则货品名称〖案例要求〗:将当前工作表产品名中的“计算机”为“电脑”。〖知识要点〗:Replace〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 替换计算机为电脑()'将当前表所有“计算机”替换为“电脑”ActiveSheet.UsedRange.Replace What:=" 计 算 机 ", Replacement:=" 电 脑 ",LookAt:=xlWholeEnd Sub以上过程中使用 UsedRange 表示对当前工作表所有区域进行替换。如果仅仅对 A列对行列替换,则可以将 UsedRange 修改为[A:A]。在使用上替换比查找简单很多,速度也更快。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“替换计算机为电脑”,则当前表所有“计算机”会被替换为“电脑”。但是“银河计算机”这类单元格则会忽略,因为代码中 LookAt 已设置为精确查找。〖语法补充〗:(1)Range.Replace 方法返回 Boolean,它表示指定区域内单元格中的字符。它的语法如下:Range.Replace(What, Replacement, LookAt, SearchOrder, MatchCase, MatchByte,SearchFormat, ReplaceFormat)其中各参数含义如下:表 11-18名称WhatReplacementLookAtRange.Replace方法含义详解描述Microsoft Excel 要搜索的字符串替换字符串可为以下 XlLookAt 常量之一:xlWhole 或 xlPartExcel VBA 程序开发自学通SearchOrderMatchCaseMatchByteSearchFormatReplaceFormat2020-2-26第 310页 /共 508页可为以下 XlSearchOrder 常量之一:xlByRows 或 xlByColumns如果为 True,则搜索区分大小写只有在Excel中选择或安装了双字节语言时,才能使用此参数。如果为True,则双字节字符只与双字节字符匹配。如果为False,则双字节字符可与其对等的单字节字符匹配该方法的搜索格式该方法的替换格式(2)Excel 的自动更正方法可以实现替换功能,但它与 Replace 方法也有本质上的区别。Replace 方法可以对任意区域实现替换,而自动更正无法仅对某个区域发生作用,而是作用于整个工作簿。11.2.8将公式添加到公式〖案例要求〗:将所有具有公式的单元格添加公式批注,批注内容为该单元格的公式。〖知识要点〗:AddComment〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 将公式添加到批注()Dim rng As RangeApplication.ScreenUpdating = FalseOn Error Resume Next'遍历所有公式单元格For Each rng In ActiveSheet.UsedRange.SpecialCells(xlCellTypeFormulas, 23)'如果存在错误(当前表没有公式)则退出程序IF Err 0 Then GoTo endd'首先清除批注(假设有的话)rng.ClearComments'添加批注,批注的文字等于该单元格的公式With rng.AddComment(rng.FormulaLocal).Visible = True '显示批注.Shape.Select'选择批注外框Selection.AutoSize = True '大小自动适应文字.Visible = False '隐藏批注End WithNext rngendd:Application.ScreenUpdating = TrueEnd Sub以上过程仅对公式所在单元格添加批注,那么工作表不存在公式时则会出错。为了防错弹出错误对话框,过程中使用了防错机制。在进行循环的第一个阶段发现有错误时就可以退出程序,从而防止弹出对话框,同时避免不必要的循环,节约时间。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将公式添加到批注”,工作表中抽所有带有公式的单元格将瞬间完成公式到批注,效果见图 11.45 所示。Excel VBA 程序开发自学通2020-2-26图 11.45第 311页 /共 508页将公式加入批注〖语法补充〗:(1)Range.AddComment 方法可为区域添加批注。其语法如下:表达式.AddComment(Text)其中 Text 参数表示添加批注的内容,可以借用 Chr(10)实现批注换行。(2)如果单元格中已经有批注,那么对工作表添加批注会产生错误。而对没有批注的单元格删除批注却不会产生错误,所以应养成添加批注之前总是删除一次原有(不管有没有)批注。(3)对批注设置“自动调整大小”时,必须在显示批注的情况下完成,所以使用“AutoSize = True”之前必须显示批注,且选择批注图形;而在之后对它进行隐藏。11.2.9填充工作日〖案例要求〗:将 A2 的日期向下填充到月底,忽略周末。〖知识要点〗:DataSeries、Resize〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 填充日期()Dim dat As Long'首先计算出本月最后一天的日期序列dat = WorksheetFunction.EoMonth([a2], 0)'将 A2 的值向下填充,按工作日、步长为 1 的方式填充到月底为止[a2].Resize(dat - [a2], 1).DataSeries Rowcol:=xlColumns, Type:=xlChronological,Date:= _xlWeekday, Step:=1, Stop:=dat, Trend:=FalseEnd Sub以上过程中重点在于计算 A2 的日期当月最后一天的日期,重置区域和设置终止值都这个日期值为基准。代码中借用了工作表函数 EoMonth 来完成,该函数是 Excel2007 自带的函数,而 Excel 2003 中需要安装分析工具库才可以使用该函数。DataSeries 方法填充日期时,使用 xlWeekday 做为参数,表示仅仅按工作日计算,忽略周六与周日的日期(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“填充日期”,单元格 A2 的日期填充前面的效果见图 11.46 和图 11.47 所示。不管 A2 的值是哪一天的日期,程序总是按该月的实际天数向下填充到月尾。Excel VBA 程序开发自学通2020-2-26第 312页 /共 508页图 11.46 原始日期图 11.47 按工作日填充后的日期〖语法补充〗:(1)Range.DataSeries 方法用于在指定区域内创建数据系列。它的具体语法如下:Range.DataSeries(Rowcol, Type, Date, Step, Stop, Trend)其中各参数含义见下表所示:表 11-19 Range.DataSeries方法参数含义详解名称数据类型RowcolVariantTypeXlDataSeriesTypeDateXlDataSeriesDateStepStopVariantVariantTrendVariant描述可以是 xlRows 或 xlColumns 常量,分别表示按行或列输入数据系列。如果省略本参数,则使用区域的大小和形状数据序列的类型如果 Type 参数为 xlChronological,则 Date 参数指示单步执行日期单位系列的步长值。默认值为 1系列的终止值。如果省略本参数, Excel将填满整个区域如果为 True,则创建一个线性趋势或增长趋势。如果为 False,则创建一个标准数据序列。默认值为 False其中第二参数 Type 用于设置填充序列的类型,有以下四种可选项:表 11-20 ange.DataSeries方法之XlDataSeriesType数据类型常量名称xlAutoFillxlChronologicalxlDataSeriesLinearxlGrowth值43-41322描述按照“自动填充”设置对系列进行填充用数据值进行填充扩展值,假定一个加法级数(例如,“1, 2”被扩展为“3, 4, 5”)扩展值,假定一个乘法级数(例如,“1, 2”被扩展为“4, 8, 16”)本例是日期序列填充,通过第三参数可以控制日期的计算方式,它有以下四种算法:表 11-21 ange.DataSeries方法之日期常量名称xlDayxlMonthxlWeekdayxlYear值1324描述日月工作日年(2)Range.Resize 属性用于调整指定区域的大小,返回 Range 对象调整后的区域。其语法如下:Excel VBA 程序开发自学通2020-2-26第 313页 /共 508页Range.Resize(RowSize, ColumnSize)在本例中,第一参数使用月尾的日期值减去 A2 的日期值,第一参数使用 1,则可以产生一个多行一列的新区域。11.2.10对区域添加四周边框〖案例要求〗:对区域添加上下左右边框,忽略内部线框。〖知识要点〗:BorderAround〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 添加四周红色粗边框()'如果选择了单元地格,则添加红色粗边框IF TypeName(Selection) = "Range" ThenSelection.BorderAround ColorIndex:=3, Weight:=xlThickEnd IFEnd Sub以上过程中对选择的对象进行判断,如果是单元格对象才添加边框。当然也可以利用“On Error Resume Next”来防错,可以产生同样的效果。(3)返回工作表,选择待添加边框的区域“B2:E10”,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加四周红色粗边框”。程序执行前后的单元格效果分别见图 11.48 和图 11.49 所示。图 11.48 添加边框前图 11.49 添加边框后〖语法补充〗:(1)Range.BorderAround 方法可以向单元格区域添加边框,并设置该新边框的Color、LineStyle 和 Weight 属性。具体语法如下:Range.BorderAround(LineStyle, Weight, ColorIndex, Color)其中四个参数的含义如下:表 11-22 Range.BorderAround参数详解名称LineStyleWeightColorIndex数据类型VariantXlBorderWeightXlColorIndex描述用于指定边框线样式的 XlLineStyle 常量之一边框粗细边框颜色,作为当前调色板的索引或作为Excel VBA 程序开发自学通Color2020-2-26第 314页 /共 508页XlColorIndex常量边框颜色,以 RGB 值表示Variant其中第二参数用于指定边框的粗细程序,有以下四种可选项:表 11-23 代表边框粗细的常量名称xlHairlinexlThinxlThickxlMedium值124-4138描述细线(最细的边框)细粗(最宽的边框)中等而第三参数用于指定边框颜色地址,可以使用 1 到 56 之间的数值,表示添加彩色边框。如果将 ColorIndex 参数设置为 xlColorIndexNone,表示无色,那么将采用默认的黑色边框。(2)BorderAround 是添加四周的边框,而 Borders 在功能上要强大一些,可以添加四周任意一边的边框,或者内部的边框。所以他们在应用范围上有所区别。11.2.11多区域合并〖案例要求〗:将选择的区域合并,如果同时选择多个区域,就合并多个区域,且合并区域时保留区域中所有字符。〖知识要点〗:Areas〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 多区域合并()Dim i As Byte, Item As Integer, Str As String'判断用户选择的对象是否单元格IF TypeName(Selection) = "Range" Then'关闭警告框Application.DisplayAlerts = False'遍历所有区域For i = 1 To Selection.Areas.Count'将区域中所有字符串起来,用空格隔开For Item = 1 To Selection.Areas(i).CountIF Len(Selection.Areas(i)(Item)) > 0 ThenStr = Str & Selection.Areas(i)(Item) & " "End IFNext Item'将一个区域合并,并字符串赋与合并后的区域Selection.Areas(i).MergeSelection.Areas(i) = StrStr = ""'将变量重置为空Next iApplication.DisplayAlerts = TrueEnd IFEnd Sub以上过程中为了防错,首先判断选择对象的类型,如果非单元格对象就出程序。然后关掉 Excel 的警告框,再对区域进行逐个合并。合并后的新区域保留了合并前的Excel VBA 程序开发自学通2020-2-26第 315页 /共 508页所有数据,中间用空格分隔。(3)返回工作表,假设工作表中有图 11.50 所示数据,按住 Ctrl 键同时选择三个区域 A2:C2、A4:B4 和 A6:C6,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“多区域合并”,三个区域合并后的效果见图 11.51 所示。图 11.50 选择三个区域图 11.51 合并后的效果〖语法补充〗:(1)Areas 代表由选定区域内的多个子区域或连续单元格块组成的集合。本例中首先统计区域的个数,然后通过 Areas(index)方式访问每个子集。11.2.12对小于 60 的成绩加虚框〖案例要求〗:将成绩表中所有不及格的成绩添加虚框。〖知识要点〗:Borders〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 对小于 60 的成绩加虚框 ()Dim rng As Range, rg As Range'遍历成绩区域For Each rng In Range("A1:D9")'如果符合条件IF rng < 60 ThenIF rg Is Nothing ThenSet rg = rngElseSet rg = Union(rg, rng) '合并找到的所有单元格End IFEnd IFNext'对合并区域添加边框With rg.Borders.LineStyle = xlDash '线型为虚线.ColorIndex = xlAutomatic.Weight = xlMedium '中等宽度End WithEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“对小于 60 的成绩加虚框”,执行过程前后,单元格状态对比如下:Excel VBA 程序开发自学通2020-2-26第 316页 /共 508页图 11.52 添加虚框前图 11.53 添加虚框后〖语法补充〗:(1)Range.Borders 属性返回一个 Borders 集合,它代表样式或单元格区域的边框。对单元格添加框线的具体语法如下:Range.Borders(XlBordersIndex).LineStyle=XlLineStyle其中参数 XlBordersIndex 表示边框位置,例如上部、底部等等,如果忽略参数则表示包括所有方向的田字型边框。可选的常量及其含义见下表:表 11-24 可用的XlBordersIndex常量列表名称xlDiagonalDownxlDiagonalUpxlEdgeBottomxlEdgeLeftxlEdgeRightxlEdgeTop值5697108xlInsideHorizontal12xlInsideVertical11描述从区域中每个单元格的左上角至右下角的边框从区域中每个单元格的左下角至右上角的边框区域底部的边框区域左边的边框区域右边的边框区域顶部的边框区域中所有单元格的水平边框(区域以外的边框除外)区域中所有单元格的垂直边框(区域以外的边框除外)而参数中的 LineStyle 表示线型,它有以下可选的常量名称,用于指定线型:表 11-25 线型列表名称xlLineStyleNonexlDoublexlDotxlDashxlContinuousxlDashDot值-4142-4119-4118-411514xlDashDotDot5xlSlantDashDot13描述无线条双线点式线虚线实线点划相间线划线后跟两个点倾斜的划线(2)如果仅仅需要对一个区域的四周添加边框,利用 Borders 可以完成,但却没有 BorderAround 方便。Excel VBA 程序开发自学通11.2.132020-2-26第 317页 /共 508页反向选择单元格〖案例要求〗:选择当前工作表中已用区域的反向区域,即选择未被选中的区域。〖知识要点〗:Select、Address〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 反向选择()'如果工作表已保护,则退出程序IF ActiveSheet.ProtectContents Then Exit Sub'如果选择对方不是单元格则退出程序IF TypeName(Selection) "Range" Then Exit SubApplication.DisplayAlerts = False'关闭删除工作表时的提示Application.ScreenUpdating = False '加快速度Dim raddress As String, taddress As String'如果已全选则退出程序IF Selection.Rows.Count = Rows.Count And Selection.Columns.Count =Columns.Count Then End'如果已用区域与选区不存在交集或者选区等于已用区域,那么就选择已用区域IFIntersect(ActiveSheet.UsedRange,Selection)IsNothingOrActiveSheet.UsedRange.Address = Selection.Address ThenActiveSheet.UsedRange.Select: Exit SubEnd IFraddress = Intersect(Selection, ActiveSheet.UsedRange).Address '记录当前选区的地址taddress = ActiveSheet.UsedRange.Address'再记录当前工作表已用区域的地址'添加一个辅助工作表,将当前已用区域对应的地址赋值 0,将前选区对应的地址赋值"=0",从而形成差异(一为常量一为公式)With Sheets.Add.Range(taddress) = 0.Range(raddress) = "=0"'记录常量的地址raddress = .Range(taddress).SpecialCells(xlCellTypeConstants, 1).Address.Delete'删除工作表End With'选择已记录的常量所对应的区域ActiveSheet.Range(raddress).SelectApplication.DisplayAlerts = TrueApplication.ScreenUpdating = TrueEnd Sub以上过程中设置了四种防错的措施:首先判断工作表是否被保护,在保护状态下无法选择反向区域;然后判断当前是否选择了单元格,未选择单元格时不存在反向区域;再之后计算选区的行数与列数,如果等于 Excel 的最大行数与最大列数,表示已全选工作表,它仍然不存在反向区域;最后判断选区与当前表已用区域的关系,如果不存在交集或者完全重合,那么也不存在反向区域。而对于其它的情况下,则使用一个辅助工作表来完成记录反向区域的地址。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“反向选择”,那么瞬间可以完成反向选择。图 11.54 和图 11.55 是反向选择前后的对比效Excel VBA 程序开发自学通2020-2-26第 318页 /共 508页果,为了图 11.55 特意对选区加了深灰色背景。图 11.54 选择 B3:B6图 11.55 反向选择区域〖语法补充〗:(1)Range.Select 方法用于选择对象。可以单元格单元格,也可以选择区域,甚至同时选择多区域。但有别的激活 Activate 方法。Activate 方法只能针对单个单元格、单个区域。(2)Range.Address 属性用于获取单元格的地址,它的语法如下:Range.Address(RowAbsolute,ColumnAbsolute,ReferenceStyle,External,RelativeTo)五个参数皆为可选参数,当忽略所有参数时表示 A1 样式的绝对引用。各参数含义如下:表 11-26 Range.Address属性参数含义列表名称RowAbsoluteColumnAbsoluteReferenceStyleExternalRelativeTo11.2.14描述如果为 True,则以绝对引用返回引用的行部分。默认值为 True如果为 True,则以绝对引用返回引用的列部分。默认值为 True引用样式。默认值为 xlA1如果为 True,则返回外部引用。如果为 False,则返回本地引用。默认值为 False如果 RowAbsolute 和 ColumnAbsolute 为 False,并且 ReferenceStyle 为xlR1C1,则必须包括相对引用的起始点。此参数是定义起始点的 Range 对象插入图片并调整为选区大小〖案例要求〗:插入一张产品图片,且图片刚好适应选区。〖知识要点〗:Top、Left、Height、Width〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 导入图片且等于选区大小()On Error Resume Next'弹出一个打开文件的对话框Dim filefilter1, filennames As String'设置文件类型为 jpg bmp png 和 giffilefilter1 = ("所有图片文件 (*.jpg;*.bmp;*.png;*.gif),*.jpg;*.bmp;*.png;*.gif")'让用户选择待插入的文件,只能单选filennames = Application.GetOpenFilename(filefilter1, , " 请 选 一 个 图 片 文 件 ", ,MultiSelect:=False)Excel VBA 程序开发自学通2020-2-26第 319页 /共 508页'插入该图片,然后设置其上边距、左边距、宽度与高度皆与选区一致With ActiveSheet.Pictures.Insert(filennames).Top = ActiveCell.Top.ShapeRange.LockAspectRatio = msoFalse.Width = Selection.Width.Left = ActiveCell.Left.Height = Selection.HeightEnd WithEnd Sub以上过程中使用了 GetOpenFilename 来获取图片文件名,然后根据图片文件全名插入该图片工作表,而不是使用插入图片的对话框 Application.Dialogs(342)。这是因为插入图片的对话框无法限制用户一次只能插入一张图片,而且插入后的图片不便于控制。GetOpenFilename 方法的优点在于随心所欲地定制文件格式,且插入后扔的图片也可以随心所欲控制它。(3)返回工作表,选择 B2:E5 区域,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“插入图片且等于选区大小”,程序将弹出一个“请选择一个图片文件”的对话框,它支持 png、jpg、gif 和 bmp 四种格式的图片文件,见图 11.56 所示。从打开的对话框中任意选择一张图片,然后程序会将该图片插入到当前表,当其上边距、左边距以及高度、宽度都是与选区一致。图 11.57 是选择的区域,而图 11.58是插入图片后的效果,它刚好适应选区。如果用户选择的是一个单元格,那么图片也会刚好嵌入该单元格中。图 11.56选择图片文件Excel VBA 程序开发自学通图 11.572020-2-26第 320页 /共 508页图 11.58选择单元格插入图片覆盖选区〖语法补充〗:(1)Range.Top 属性返回或设置单元格的上边距(以磅为单位)。而 Range.Left属性返回或设置单元格的左边距(以磅为单位)。(2)Range.Width 属性返回一个区域的宽度(以磅为单位)。Range.Height 属性返回一个区域的高度(以磅为单位)。(3)Application.GetOpenFilename 方法可以显示一个标准的“打开”对话框,并获取用户文件名。它的语法如下:Application.GetOpenFilename(FileFilter, FilterIndex, Title, ButtonText, MultiSelect)其中五个参数的含义如下:表 11-27名称FileFilterFilterIndexTitleButtonTextMultiSelectApplication.GetOpenFilename方法含义详解描述一个指定文件筛选条件的字符串指定默认文件筛选条件的索引号,取值范围为 1 到由 FileFilter 所指定的筛选条件数目。如果省略该参数,或者该参数的值大于可用筛选条件数,则使用第一个文件筛选条件指定对话框的标题。如果省略该参数,则标题为“打开”仅限 Macintosh如果为True,则允许选择多个文件名。如果为False,则只允许选择一个文件名。默认值为FalseApplication.GetOpenFilename 的一个极大的优点是可以任意筛选文件格式,也可以任何组合多种格式的文件。本例中默认显示四种格式的图片,如果需要分开显示,即一次筛选一种格式的图片,那么可以按以下方式设置筛选条件:filefilter1 = "位图 (*.bmp),*.bmp,jpg 图片 (*.jpg),*.jpg,png 图片 (*.png),9.png,Gif动画 (*.gif),*.gif"设置效果如下,显示为四种格式,默认显示第一种,而对话框中显示的图片也根据用户选择的文件类型而变化。图 11.59显示四种格式的图片如果需要既显示多个选项,其中某个选项又可以包含多种格式,那么需要对包含多种格式的选项利用分号连接。而且同时可以修改 Application.GetOpenFilename 的第二参数设置默认选项为 2。例如以下代码可以产生图 11.60 中的效果:filefilter1 = " 位 图 (*.bmp),*.bmp, 其 它 图 片 (*.jpg;*.png),*.jpg;*.pgn,Gif 动 画Excel VBA 程序开发自学通2020-2-26第 321页 /共 508页(*.gif),*.gif"图 11.6011.2.15显示三个选项共五种格式的文件选择当前表已用区域的奇/偶数行〖案例要求〗:选择当前表已用区域的奇/偶数行,由用户指定是奇数还是偶数。〖知识要点〗:Columns、Rows〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 选择奇偶数行()Dim i As Integer, rng As Range, Ji_Ou As ByteOn Error Resume NextStar:'设定一个标签,当程序出错时返回此处'指定奇偶Ji_Ou = Application.InputBox("输入 1:选择奇数行" & Chr(10) & "输入 2:选择偶数行", "确认奇偶性", 1, , , , , 1)'如果输入的值超出 Byte 范围则返回 Star 重新输入IF err.Number > 0 Then err.Clear: GoTo Star'如果输入值不等于 1 而且不等于 2 则重新输入IF Ji_Ou 1 And Ji_Ou 2 Then GoTo Star'根据用户的输入值初始化变理Set rng = Cells(Ji_Ou, 1).Resize(1, ActiveSheet.UsedRange.Columns.Count)'循环,合并所有奇/偶数行For i = Ji_Ou To ActiveSheet.UsedRange.Rows.Count Step 2Setrng=Union(rng,Cells(i,1).Resize(1,ActiveSheet.UsedRange.Columns.Count))Next irng.Select'选择目标End Sub不要不是声明为变体型,那么让用户输入值时都需要防错,避免用户输入的值超过允许的范围而中断程序。本例中除了限制用户水能超过 Byte 的范围外,还人限制输入值不能是 1 或者 2 以外的任何值,否则会漏选部分区域。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“选择奇偶数行”,将弹出图 11.60 所示对话框,让用户决定是选择奇数行还是偶数行,其默认值为 1,表示奇数行。如果用户选择了奇数行,那么执行结果见图 11.61 所示。Excel VBA 程序开发自学通图 11.602020-2-26选择奇偶性第 322页 /共 508页图 11.61选择奇数行〖语法补充〗:(1)Range.Rows 属性返回一个 Range 对象,它代表指定单元格区域中的行。(2)Range.Columns 属性返回一个 Range 对象,它代表指定单元格区域中的列。11.2.16删除当前表的空行〖案例要求〗:将当前表已用区域中的空行删除。〖知识要点〗:Row、Column〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 删除空行()'批量删除空行,适用于任何行任何列Dim arr(), rng, lng As Long, lng2 As Long, i, jApplication.ScreenUpdating = False'记录当前表已用区域的行数和列数lng = ActiveSheet.UsedRange.Rows.Countlng2 = ActiveSheet.UsedRange.Columns.CountReDim arr(1 To lng) '声明一个数组,其下标为 1,上标等于已用区域的行数rng = ActiveSheet.UsedRangeFor i = 1 To lng '遍历每行For j = 1 To lng2 '遍该行的每列'如果该行非空则在数组中记录序号,否则赋值为空'判断是否空行也可以使用工作表函数 Counta,大家可以根据习惯选择IF rng(i, j) "" Then arr(i) = i: Exit ForNext jNext' 在已用区域的右边一列添加辅助列,该列存放数组的值'然后以该列为基准,对整个已用区域进行排序,从而实现将空白行移到最后面WithCells(ActiveSheet.UsedRange.Row,ActiveSheet.UsedRange.Column+lng2).Resize(lng, 1).Value = WorksheetFunction.Transpose(arr)ActiveSheet.UsedRange.SortKey1:=Cells(ActiveSheet.UsedRange.Row,ActiveSheet.UsedRange.Column + lng2).Value = ""End WithApplication.ScreenUpdating = TrueEnd Sub删除空行有很多方法,包括逐行检检,并将空行删除,以及合并所有空行再一次Excel VBA 程序开发自学通2020-2-26第 323页 /共 508页性删除,还有本例中排序方式将空行移到最后面。经过了多次测试,排序方式效率最高。对于数组的声明和使用,在本书第 13 章和 14 章将进行详细讲解。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除空行”,工作表中的所有空行都被删除。图 11.62 是删除前的测试数据,图 11.63 是删除空行的效果。笔者利用 2000 行数据测试,执行时间不到一秒钟。图 11.62删除空行前图 11.63删除空行后〖语法补充〗:(1)Range.Row 属性返回区域中第一个子区域的第一行的行号。(2)Range.Column 属性返回区域中第一个子区域的第一列的列号。例如[b2:d100].Row 等于 2,而[b2:d100].Column 也等于 2,即取 B2 的行号与列号。11.2.17删除重复值〖案例要求〗:图 11.64 中为参赛人员名单,其中部分人员参加了多个项目的比赛。现需提取参赛人员名单,忽略重复值。〖知识要点〗:RemoveDuplicates〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 删除重复值 1() '2007 专用With Range("C1:C11")'将所有参赛人员名单复制到 C 列.Value = Range("A1:A11").Value'删除重复值.RemoveDuplicates Columns:=1, Header:=xlYesEnd WithEnd Sub以上过程将 A 列的名单复制到 C 列,然后对 C 列取唯一值。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除重复值”,执行后效果见图 11.65 所示,重复值已经删除。Excel VBA 程序开发自学通图 11.64 参赛者列表2020-2-26第 324页 /共 508页图 11.65 获取唯一值(4)以上代码中的 RemoveDuplicates 是 Excel 2007 新增功能,如果用户使用 Excel2003,或者希望代码可以在 Excel 2003 和 Excel 2007 通用,那么可以使用以下代码:Sub 删除重复值 2() '2003 和 2007 通用Dim rng As Range, Item As Integer'循环 A 列所有姓名For Each rng In [A1:A11]'如果 C 列不存在则将该姓名复制到 C 列IF WorksheetFunction.CountIF([C1:C11], rng) = 0 ThenItem = Item + 1Cells(Item, "C") = rngEnd IFNextEnd Sub以上过程利用工作表函数 Countif 判断姓名是否重复,此方法在 Excel 2003 和 2003、2007 都通用。(5)以上方式相当于使用以辅助区域,如果需要在原区域返回唯一值,那么在其它区域产生唯一值,再复制回原区域显然效率较低。可以改用以下过程:Sub 删除重复值 3() '2003 和 2007 通用Dim rng, Item As Integer, arr'将单元赋值到数组(存入内存中)arr = [A1:A11]'清空原来的数据[A1:A11] = ""'遍历数组中所有值For Each rng In arr'如果 A 列不存在则将数组中对应的值复制到 A 列IF WorksheetFunction.CountIF([A1:A11], rng) = 0 ThenItem = Item + 1Cells(Item, "A") = rngEnd IFNextEnd Sub〖语法补充〗:(1)Range.RemoveDuplicates 方法用于从区域中删除重复的值,可以是多条件,它的语法如下:Excel VBA 程序开发自学通2020-2-26第 325页 /共 508页Range.RemoveDuplicates(Columns, Header)其中两个可选参数的含义如下:表 11-28 Range.RemoveDuplicates方法参数详解名称ColumnsHeader描述包含重复信息的列的索引数组。如果没有传递任何内容,则假定所有列都包含重复信息指定第一行是否包含标题信息。xlNo是默认值;如果希望Excel确定标题,则指定xlGuess(2)RemoveDuplicates 可以实现多条件去复重复值,即同时在多个列都重复才算重复。例如:图 11.66 中,A、B 列同时重复才算重复,,那么满足条件的是灰色背景的区域,其中“甲”虽然出现三次,但第三次出现时 B 列与前面的不同则不符合要求。双条件取唯一值代码如下:Sub 删除重复() '2007 专用[A1:B11].RemoveDuplicates Columns:=Array(1, 2), Header:=xlYesEnd Sub以上过程中 RemoveDuplicates 的第一参数使用数组“Array(1, 2)”,表示同时两列都产生重复才满足条件。图 11.66 双列重复的参赛列表11.2.18图 11.67 双条件取唯一值将选区导出为图片〖案例要求〗:将选区导入到硬盘中,保存为图片。〖知识要点〗:RemoveDuplicates〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 将选区导出成图片()Dim adds As String, filennames'如果未选择单元格则退出程序IF TypeName(Selection) "Range" Then Exit Sub'记录选区地址,且将冒号修改为-,否则无法做为文件名adds = Replace(Selection.Address(0, 0), ":", "-")'将文件复制为图片Excel VBA 程序开发自学通2020-2-26第 326页 /共 508页Selection.CopyPicture 1, 2'贴粘图片ActiveSheet.Pictures.Paste.SelectWith Selection'贴粘图片.Copy'新建一个图表WithActiveSheet.ChartObjects.Add(0,0,Selection.Width,Selection.Height).Chart.Paste '将图片贴于图表中'弹出一个保存图片的对话框,让用户录入新图片名字,默认单元格地址(冒号替换成-)filennames = Application.GetSaveAsFilename(adds, "JPG 格 式(*.JPG),*.jpg,BMP 格式(*.BMP),*.bmp,PNG 格式 (*.PNG),*.png", 2, "保存图片文件")'将图表导出成图片.Export filennames.Parent.Delete '删除图表End With.Delete '删除图片End With'打开存放图片的文件夹Shell"explorer.exe"&Left(filennames,Len(filennames)InStr(StrReverse(filennames), "\")), vbMaximizedFocusEnd Sub以上过程首先将选区复制为图片、粘贴到工作表,然后新建一个与选区相同大小的图表,并将图片贴于图表中,最后通过 Export 方法将图表导出到硬盘中,保存为图片。默认的图片名为选区地址。保存图片时提供三种可选格式,BMP 格式最清晰,建议使用。(3)返回工作表,假设需要史载图 11.68 中 A1:B2 区域转换成图片,那么选择该区域,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将选区导出成图片”,在弹出的保存图片对话框中保持默认即可,生成的图片利用 Win XP 的图片浏览器查看效果如图 11.69 所示:图 11.68 复制 A1:B2图 11.69 已导出的名为 A1-B2 的图片〖语法补充〗:(1)Range.CopyPicture 方法将所选对象作为图片复制到剪贴板。它的语法如下:Excel VBA 程序开发自学通2020-2-26第 327页 /共 508页Range.CopyPicture(Appearance, Format)其中第一参数表示图片的复制方式,包括以下两种方式:表 11-29 图片复制方式名称xlPrinterxlScreen值21描述图片按其打印效果进行复制图片尽可能按其屏幕显示进行复制第二参数表示图片的格式,包括以下两种方式:表 11-30 图片格式名称xlBitmapxlPicture值2-4147描述位图(.bmp、.jpg、.gif)绘制图片(.png、.wmf、.mix)本例中“Selection.CopyPicture 1, 2”表示将选区按屏幕显示进行复制,存为位图。(2)利用本例方法,可以批量地将工作表中所有图片导出到硬盘,在本书最后章节详细介绍。11.2.19删除超链接〖案例要求〗:根据用户的输入值决定删除当前表的超级链接或者删除所有工作表的超级链接。〖知识要点〗:Hyperlinks〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 删除超级链接()Dim sht As Worksheet, Opt As Byte'防错On Error Resume NextStar: '设置签标'让用户决定删除超级链接的范围Opt = Application.InputBox("输入 1:删除当前表超级链接" & Chr(10) & _"输入 2:删除所有工作表超级链接", "确认范围", , , , , , 1)'如果有错误则返回,让用户重新输入IF Err 0 Then err.Clear: GoTo StarIF Opt = 1 Then'输入 1 时删除当前表所有链接Cells.Hyperlinks.DeleteElse'否则逐个工作表删除链接For Each sht In Sheetssht.Cells.Hyperlinks.DeleteNext shtEnd IFEnd Sub以上过程中用户输入 1 时删除当前表所有超级链接,录入 2 到 255 或者 0 时删除当前工作簿中所有工作表的超级链接。(3)返回工作表,在单元格中输入 http://baidu.com 和 andy_qc@163.com,ExcelExcel VBA 程序开发自学通2020-2-26第 328页 /共 508页会自动将它们转换成超级链接,单击时可以打开邮件或者网址,见图 11.70 所示:(4)使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除超级链接”,输入 1 后单击“确定”按钮,当前表的所有超级链接瞬间消失,见图 11.71.图 11.70 输入网址或邮件地址时自动产生超链接图 11.71 删除链接后〖语法补充〗:(1)Range.Hyperlinks 属性返回 Hyperlinks 集合,它代表区域中的所有超链接。通过 Hyperlinks.coint 可以统计当前表超链接个数。(2)如果需要获取当前表所有超级链接地址Sub 获取超级链接地址()Dim iFor i = 1 To Cells.Hyperlinks.CountMsgBox Cells.Hyperlinks(i).AddressNextEnd Sub11.2.20选择本表所有合并单元格〖案例要求〗:选择当前表中所有合并单元格。〖知识要点〗:MergeCells〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 全选合并单元格()Dim rng As Range, rngg As Range'遍历已用区域For Each rng In ActiveSheet.UsedRange'如果具有合并属性IF rng.MergeCells Then'合并所有合并单元格为一个区域IF rngg Is Nothing ThenSet rngg = rngElseSet rngg = Union(rngg, rng)End IFEnd IFNext'如果有合并单元格则选择合并单元格IF Not rngg Is Nothing Then rngg.SelectEnd Sub以上过程遍历已用区域,将所有具有合并属性的单元格合并成一个区域,然后选Excel VBA 程序开发自学通2020-2-26第 329页 /共 508页择该区域。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“全选合并单元格”,如果工作表中有合并单元格,将自动选择所有合并区域。图 11.72即为选择所有合并区域后的状态。为了突出选区,特意利用灰色填充选区。图 11.72 选择所有合并区域〖语法补充〗:(1)Range.MergeCells 属性用于判断单元格是否处于合并区域中,其值为 True或 Fasle。11.2.21朗读选区字符〖案例要求〗:将选区的单元格字符朗读出来。〖知识要点〗:Speak〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 阅读选区所有字符()'如果选择对象是单元格则朗读IF TypeName(Selection) = "Range" ThenSelection.SpeakEnd IFEnd Sub执行以上程序需要确保电脑装有声卡和连接音响,而且在所安装的 OFFICE 非精简版,否则可能不俱备朗读功能。朗读单元格时区分中文和英文。如果发音引擎是英文,那么它会忽略中文,而英文会按照单词朗读出来;如果安装了中文的发音引擎,那么它可以朗读中文与英文,只不过对于英文不再是按单词朗读,而是逐个字母朗读。(3)返回工作表,选择待阅读的区域,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“阅读选区所有字符”,音箱中可以传出朗读的声音。〖语法补充〗:(1)Range.Speak 方法表示按行或列的顺序朗读单元格区域。它的语法如下:Range.Speak(SpeakDirection, SpeakFormulas)其中第一参数表示朗读的方向,按行或按列;第二参数表示公式的朗读方式,如果为 True 则朗读公式,如果为 False,则始终将朗读公式的值。Excel VBA 程序开发自学通11.2.222020-2-26第 330页 /共 508页隐藏所有公式结果为错误的单元格〖案例要求〗:图 11.73 中部分公式的运算结果为错误值,现需要将所有错误隐藏,仅显示空白。〖知识要点〗:Errors、NumberFormatLocal〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 隐藏所有错误公式()Dim rng As Range'遍历 C2:C11 区域For Each rng In [c2:c11]'将单元格的字体色设为单元格背景色rng.Font.Color = rng.Interior.Color'设置单元格的数字格式,让错误值以外的值显示为黑色rng.NumberFormatLocal = "[黑色]G/通用格式"'忽略错误,隐藏绿色小三角rng.Errors(1).Ignore = TrueNextEnd Sub以上过程首先将所有单元格的字体色设置与背景色一致,然后利用单元格的数字格式将非错误值的字体设置为黑色,鉴于数字格式仅仅对非错误值生效,那么通过以上设置后就可以实现隐藏错误值。最后去掉绿色小三角。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏所有错误公式”,C2:C11 区域中的所有错误值都将隐藏起来。图 11.73 和图 11.74是隐藏错误前后的对比效果,图 11.73 公式中的错误值图 11.74 隐藏错误〖语法补充〗:(1)Range.NumberFormatLocal 属性可以返回或设置一个单元格数字格式的格式代码。可以对单元格的数字格式赋值从而改变其显示值。例如单元格中当前显示“2008-9-9”,通过以下代码可以将它的显示值修改为“星期二”:Sub 格式化为星期()Range("A20").NumberFormatLocal = "AAAA"End Sub(2)Range.Errors 代表单元格中的错误对象,通过设置其 Ignore 属性可以控制是否显示小绿色三角符号。Excel VBA 程序开发自学通11.2.232020-2-26第 331页 /共 508页快速添加日期批注且自动缩放〖案例要求〗:利用快捷键在当前单元格生成日期批注,且批注外框刚才适应日期。〖知识要点〗:Comment.Delete、AddComment〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 快速添加日期批注()'关闭屏幕刷新,防止闪屏Application.ScreenUpdating = False'如果选择对象不是单元格则退出程序IF TypeName(Selection) "Range" Then Exit Sub'删除原有批注(如果有的话)ActiveCell. ClearComments'添加批注,内容为日期With ActiveCell.AddComment(Date & "").Visible = True '让批注可见.Shape.Select '选择批注外框Selection.AutoSize = True '自动调整大小.Visible = False '隐藏批注End WithApplication.ScreenUpdating = TrueEnd SubSub 设置快捷键()'为过程指定快捷键Application.OnKey "^e", "快速添加日期批注"End Sub第一个过程中需要对活动单元格添加批注,那么必须先删除已有批注,否则只能在已用批注的末尾添加新的文本。对批注的外框设置为自动调整大小时,需要让批注可见,且选择该 Shape,否则会设置不成功。第二个过程用于对第一个过程设置快捷键。(3)光标定位于第二个过程中,按下快捷键【F5】执行程序;(4)返回工作表,激活任意单元格后按下快捷键【Ctrl+E】,将在该单元格产生日期批注,见图 11.75 所示:图 11.75日期批注〖语法补充〗:(1)Range.AddComment 方法添加批注时其参数必须是文本,使用日期和数值都会产生错误。例如以下两种方式都会产生是错误:ActiveCell.AddComment 123ActiveCell.AddComment DateExcel VBA 程序开发自学通2020-2-26第 332页 /共 508页纠正错误可以采取两种方式:类型转换函数及连接空文本。ActiveCell.AddComment CStr(Date)ActiveCell.AddComment Date&""修改后的两句代码都可以正常执行。11.2.24以逗号分为隔符将文本分列〖案例要求〗:将单元格中的字符串以逗号为分隔符分置于多单元格中。〖知识要点〗:TextToColumns〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 按分隔符将单元格分置多单元格()[A1].TextToColumns Comma:=True, DataType:=xlDelimited, ConsecutiveDelimiter:=True,Space:=TrueEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“按分隔符将单元格分置多单元格”,A1 的文本中如果有逗号那么会以逗号为分隔符分置于多单元格中。图 11.76 和图 11.77 是分列后前的数据:图 11.76分列前图 11.77分列后〖语法补充〗:(1)Range.TextToColumns 方法可以将包含文本的一列单元格分解为若干列。语法如下:Range.TextToColumns(Destination, DataType, TextQualifier, ConsecutiveDelimiter,Tab, Semicolon, Comma, Space, Other, OtherChar, FieldInfo, DecimalSeparator,ThousandsSeparator, TrailingMinusNumbers)它的 14 个可选参数含义见下表:表 11-31名称DestinationDataTypeTextQualifierConsecutiveDelimiterTabSemicolonCommaRange.TextToColumns方法参数详解描述一个Range对象,指定Excel放置结果的位置。如果该区域大于一个单元格,则使用左上角的单元格将被拆分到多列中的文本的格式指定是将单引号、双引号用作文本分隔符还是不使用引号如果为True,则Excel将连续分隔符视为一个分隔符。默认值为 False如果为True,则DataType为xlDelimited 并将制表符作为分隔符。默认值为 False如果为True,则DataType为xlDelimited 并将分号作为分隔符。默认值为False如果为True,则DataType为xlDelimited 并将逗号作为分隔符。默认值为FalseExcel VBA 程序开发自学通2020-2-26第 333页 /共 508页如果为True,则DataType为xlDelimited 并将空格字符作为分隔符。默认值为False如果为True,则DataType为xlDelimited 并将OtherChar 参数指定的字符作为分隔符。默认值为 False(如果Other为True,则为必选项)。当Other为True 时的分隔符。如果指定了多个字符,则仅使用字符串中的第一个字符而忽略剩余字符包含单列数据相关分列信息的数组。对该参数的解释取决于 DataType的值。如果此数据由分隔符分隔,则该参数为由两元素数组组成的数组,其中每个两元素数组指定一个特定列的转换选项。第一个元素为列标(从 1 开始),第二个元素是 xlColumnDataType 的常量之一,用于指定分列方式SpaceOtherOtherCharFieldInfoDecimalSeparator识别数字时,Excel使用的小数分隔符。默认设置为系统设置ThousandsSeparatorTrailingMinusNumbers识别数字时,Excel 使用的千位分隔符。默认设置为系统设置以减号字符开始的数字(2)如果不用分列,而是使用数组函数也可以完成的。代码如下:Sub 分列()Dim str As String, istr = [a1].Text '将 A1 的值存入内存中'将 A1 的值以逗号分分隔符转换成数组,然后遍历数组所有元素For i = 1 To UBound(Split(str, ",")) + 1'从数组中逐个取出值存入单元格Cells(1, i) = Split(str, ",")(i - 1)NextEnd Sub(3)分列方法只置的分号是半角状态下的,如果单元格中使用以全角逗号,那么本例的代码需要修改。11.2.25生成二级下拉选单〖案例要求〗:利用图 11.78 中 D、E 列的数据在单元格 A1 和 B1 产生二级下拉菜单,面且这两个下拉菜单必须有关联性,即第二个菜单跟随第一个单元格的显示值相应地变化。〖知识要点〗:Validation〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 生成二级菜单()Dim i, Str'提取不重复的省名,组成一个字符串For i = 1 To Cells(Rows.Count, "D").End(xlUp).RowIF WorksheetFunction.CountIF(Cells(1, "D").Resize(i, 1), Cells(i, "D")) = 1 ThenStr = Str & Cells(i, "D") & ","Next i'将字符串加到有效性列表With Range("A1").Validation.Delete '删除以前的设置(假设有的话).Add Type:=xlValidateList, Formula1:=Left(Str, Len(Str) - 1) '自定义数据有效性来源Excel VBA 程序开发自学通2020-2-26第 334页 /共 508页.ShowInput = True '显示下拉框Range("A1") = Split(Left(Str, Len(Str) - 1), ",")(0)End WithEnd Sub'设置其默认值以上过程用于产生 A1 的下拉菜单,B 的数据有效性需要在工作表事件中完成。双击工作表名进入工作表事件代码窗口,然后输入以下事件过程:Private Sub Worksheet_Change(ByVal Target As Range)On Error Resume NextDim i, rng As Range, Str As String'只有允许 A1 触发工作表事件IF Target.Address "$A$1" Or Target = "" Then Exit SubSet rng = Range("D1:D" & Cells(Rows.Count, "D").End(xlUp).Row)'串连该省的市名For i = 1 To rng.Rows.CountIF rng(i, 1) = [a1] Then Str = Str & "," & rng(i, 2)Next'将市名添加到 B1 的数据有效性With Range("B1").Validation.Delete.Add Type:=xlValidateList, Formula1:=Right(Str, Len(Str) - 1).ShowInput = True'设置默认市名Range("B1").Value = Split(Right(Str, Len(Str) - 1), ",")(0)End WithEnd Sub以上过程是工用表级 Change 事件,当 A1 的值产生变化时,到工作表中 D 列查找 A1 的值,找到后将其 E 列中对应的市名串连起来,并做为数据有效性的来源。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成二级菜单”,此时 A1 产生默认值“广东”,B1 则产生默认值“广州”;(4)当单击单元格 A1 时会产生下拉菜单,罗列出所有省名,见图 11.78 所示;如果选择“四川”,那么 B1 单元格将产生默认值“内江”,同时单击单元格 B1 时会产生与四川关联的所有市名,见图 11.79 所示:图 11.78 一级菜单图 11.79 二级菜单〖语法补充〗:(1)Range.Validation 属性返回一个 Validation 对象,该对象表示指定区域的数据有效性检验。对单元格添加有效性验证的语法如下:表达式.Add(Type, AlertStyle, Operator, Formula1, Formula2)五个参数的含义见下表所示:表 11-32 Range.Validation.Add方法参数详解Excel VBA 程序开发自学通名称Type数据类型XlDVTypeAlertStyleVariantOperatorVariantFormula1VariantFormula2Variant2020-2-26第 335页 /共 508页描述有效性验证类型有效性验证警告的样式。可为以下 XlDVAlertStyle 常量之一:xlValidAlertInformation、xlValidAlertStop 或 xlValidAlertWarning数据有效性验证运算符。可为以下XlFormatConditionOperator常量之一:xlBetween、xlEqual、xlGreater、xlGreaterEqual、xlLess、xlLessEqual、xlNotBetween 或xlNotEqual数据有效性验证等式中的第一部分当Operator为xlBetween或xlNotBetween 时,数据有效性验证等式的第二部分(其他情况下,此参数被忽略)其中参数与有效性验证的类型相关,类型不同参数也不同。它们之间的关系如下:表 11-33 有效性验证类型与参数有效性验证类型xlValidateCustomxlInputOnlyxlValidateListxlValidateWholeNumber、xlValidateDate、xlValidateDecimal、xlValidateTextLength 或xlValidateTime参数Formula1必需,忽略 Formula2。Formula1 必须包含一个表达式,数据项有效时该表达式的值为 True,数据项无效时,该值为 False使用AlertStyle、Formula1 或 Formula2Formula1必需,忽略 Formula2。Formula1 必须包含以逗号分隔的值列表,或对该列表的工作表引用必须指定 Formula1 或 Formula2 之一,或两者均指定(2)如果单元格中已经有有效性设置,那么需要删除以前的设置信息,否则会将生错误。11.2.26将产量批量转换成下拉菜单〖案例要求〗:图 11.80 中 B 列产品的产地,利用 VBA 将其转换成下拉菜单,有多少个地名即转换为多少个下拉菜单,而且在需要时可以还原。〖知识要点〗:Validation.Formula1〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 将产地转换成菜单()Dim rng As Range'遍历区域For Each rng In Range("B2:B6")With rng.Validation.Delete '删除以前的设置(假设有的话)'自定义数据有效性来源,将/转换成逗号即可.Add Type:=xlValidateList, Formula1:=Replace(rng.Text, "/", ",").ShowInput = True '显示下拉框rng = Split(rng.Text, "/")(0) '设置其默认值End WithNext rngExcel VBA 程序开发自学通2020-2-26第 336页 /共 508页End Sub以上过程首先将产地中的“/”替为成“,”,然后添加到数据有效性的公式中。然后为单元格设置默认值。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将产地转换成菜单”。图 11.80 和图 11.81 是地名转换前后的对比效果。图 11.80 转换前图 11.81 转换后(4)如果在某些条件下,需要将下拉菜单还原为图 11.80 所示状态,那么可以改用以下过程:Sub 还原()Dim rng As Range'遍历区域For Each rng In Range("B2:B6")'还原产地rng = Replace(rng.Validation.Formula1, ",", "/")'删除有效性设置rng.Validation.DeleteNext rngEnd Sub〖语法补充〗:(1)使 Validation 时,根据有效性的类型,可以有 Formula1,可能有 Formula2,也可能同时有 Formula1 和 Formula2。(2)当单元格的有效性类型为序列 时,Validation.Formula1 中的公式应该使用半角状态下的逗号,如果使用全角逗号将无法产生多级菜单。11.2.27设计一个简单放大镜〖案例要求〗: 选择任意单元格时产生一个放大镜,将选区的值放大 N 倍显示。〖知识要点〗:Activate、CopyPicture〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:'声明 Worksheet_SelectionChange 事件Private Sub Worksheet_SelectionChange(ByVal Target As Range)'禁止递归Application.EnableEvents = FalseOn Error Resume Next '防错'删除所有图片ActiveSheet.DrawingObjects.Delete'将选区复制到图片Selection.CopyPicture Appearance:=xlScreen, Format:=xlPicture'选择右边三列Excel VBA 程序开发自学通2020-2-26第 337页 /共 508页Target.Cells(1, Selection.Columns.Count).Offset(0, 3).Select'贴选择图片ActiveSheet.Pictures.Paste.Select'设置图片的格式With Selection.ShapeRange.ScaleWidth 2, msoFalse, msoScaleFromTopLeft'增大两倍,你可以根据自己需求调整.Fill.ForeColor.SchemeColor = 3 '设置前景色.Line.Weight = 3# '指定边框宽度.Line.Style = msoLineSingle '线型.Line.ForeColor.SchemeColor = 4 '外框线颜色End WithTarget.Activate '激活单元格Application.EnableEvents = True '还原提示End Sub以上过程使用了工作表级别的 SelectionChange 事件,当选择单元格时触发事件。工具首先将选区转换成图片,然后将图片粘贴到右边三个单元格的位置,并对图片设置格式,包括边框、填充色和大小,最后返回原选择区域。(3)返回工作表,选择任意单元格或者区域,在右边第三列处会产生放大两倍的显示效果,该倍数可调。图 11.82 放大多行一行数据图 11.83 放大多行多列数据〖语法补充〗:(1)CopyPicture 方法将单元格转成图片时,如果不需要打印出来,那么尽量使用“图片尽可能按其屏幕显示进行复制” 即参数 xlScreen。(2)Range.Activate 方法用于激活一个单元格,不管其前置对象包括多少个单元格,仅激活其左上角单元格。11.3 Names 对象应用案例Names 即为名称对象。可以将单元格、区或转换成名称,也可以将公式、常量转换成名称。将区域转换成名称应用于代码中,可以让区域的表示法更具形象化,而公式或者数组常量转换成名称则可以简化输入,甚至用于突破 Excel 的公式长度限制。本节将介绍 Names 在工作中的一些常见应用。Excel VBA 程序开发自学通11.3.12020-2-26第 338页 /共 508页罗列当前工作簿的所有名称〖案例要求〗:将当前工作簿中所有定义的名称罗列在工作表。〖知识要点〗:ListNames〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 罗列当前工作簿名称()[A1].Resize(Names.Count, 2).ListNamesEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“罗列当前工作簿名称”,工作表中 A 列将出现当前工作簿所有名称,而 B 列是每个名称对应的引用值,见图 11.84 所示。图 11.84 罗列所有名称及其引用位置〖语法补充〗:(1)Range.ListNames 方法用于从指定区域的第一个单元格位置开始,将所有未隐藏的名称的列表粘贴到工作表上。本节关于 Names 对象的所有实例代码参见光盘:..\ 第十一章\Names.xlsm11.3.2利用名称引用其它表数据〖案例要求〗:在 Excel 2003 中无法引用其它工作表数据作为有效性的数据源,而 Excel 2007 对有效性做了改进,可以引用其它工作表数据。现要求借用名称实现引用其它工作表数据做为有效性数据源,从而实现代码在 Excel 2003 和 Excel 2007 通用。〖知识要点〗:Add〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 引用其它表区域做为有效性系列数据源 1() '2007 专用With Range("A2").Validation.Delete.Add Type:=xlValidateList, Formula1:="=ListNames!$A$1:$A$9".IgnoreBlank = True.InCellDropdown = TrueExcel VBA 程序开发自学通2020-2-26第 339页 /共 508页.ShowInput = TrueEnd WithEnd Sub在 Excel 2007 中,以上代码工作正常,引用 ListNames 工作表中的数据做为当前表 A2 的数据有效性来源,但是在 Excel 2003 中执行会产生错误。为了解决定个问题,需要借用名称来突破,修改后的代码如下:Sub 引用其它表区域做为有效性系列数据源 2() '借用名称实现与 2003 通用'在工作簿中添加一个名称,引用 ListNames 工作表中的 A1:A9 数据ActiveWorkbook.Names.AddName:="数据源RefersToR1C1:="=ListNames!R1C1:R9C1", Visible:=False'设置数据有效性,引用该名称With Range("A2").Validation.Delete.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Formula1:="=数据源".IgnoreBlank = True.InCellDropdown = True.ShowInput = TrueEnd WithEnd Sub",以上过程在工作簿中建一个隐藏的名称,其数据源为其它工作表,然后将该名称做为数据有效性的引用源,从而实现引用其它工作表数据,在 Excel 2003 和 Excel 2007中通用。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“引用其它表区域做为有效性系列数据源 2”,如果工作表 ListNames 中 A1:A9 非空,那么 A2 单元格将产生下拉菜单。图 11.85 引用其它表数据做为有效性数据源〖语法补充〗:(1)Names.Add 方法用于定义新名称,如果不带前置对象,那么默认为工作簿级名的名称。它的语法如下:Names.Add(Name, RefersTo, Visible, MacroType, ShortcutKey, Category, NameLocal,RefersToLocal, CategoryLocal, RefersToR1C1, RefersToR1C1Local)(2)如果添加名称时不引用单元格,而是直接引用某个常量,那么可以对RefersToR1C1 参数写入相应的公式即可:Names.Add Name:="圆周率", RefersToR1C1:="=3.1415926"该代码添加一个名为“圆周率”、值为“3.1415926”的名称。Excel VBA 程序开发自学通11.3.32020-2-26第 340页 /共 508页隐藏当前工作簿包含“A”的所有名称〖案例要求〗:图 11.80 中 B 列产品的产地,利用 VBA 将其转换成下拉菜单,有多少个地名即转换为多少个下拉菜单,而且在需要时可以还原。〖知识要点〗:Visible〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 隐藏含 A 的名称()'声明一个名称对象变量Dim nm As Name'遍历名称For Each nm In Names'如果名称中包括“A”则隐藏IF nm.Name Like "*A*" Then nm.Visible = FalseNext nmEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏含 A 的名称,那么过名称管理器无法再查看或者编辑包括“A”的名称。〖语法补充〗:(1)Name.Visible 属性确定名称是否可见,当赋值为 True 时可见,赋值为 False时不可见。11.3.4借用名称将区域数据引用到组合框〖案例要求〗:向列表框添加区域中的数据。〖知识要点〗:Add〖实现步骤〗:(1)在工作表中单击功能区【开发工具】\【插入】\【组合框(ActiveX 控件)】,并在工作表中拖放,从而添加一个组合框;(2)在 A1:A8 录入待显示在组合框中的数据,见图 11.86 所示;(3)进入 VBE 界上面,单击菜单【插入】\【模块】;(4)在模块代码窗口输入以下代码:Sub 设置引用源 1()Dim cell As Range'遍历选区,将值逐个加入组合框For Each cell In [a1:a8]Sheets("Add2").ComboBox1.AddItem cellNextEnd Sub以上代码可以实现将 A1:A8 的值逐个添加到组合框中,执行过程后单击组合框时可以产生下拉列表,见图 11.87 所示。然而循环的方式效率较差,利用名称可以有效地处理这个问题继续录入以下代码:Sub 设置引用源 2()'添加名称,引用 A1:A8 的数据Excel VBA 程序开发自学通2020-2-26第 341页 /共 508页Names.Add "区域", [a1:a8]'将名称赋值组合框Sheets("Add2").ComboBox1.ListFillRange = "区域"End Sub执行以上过程后,可以实现同样效果,但借用名称的优势在于不需要循环而是一次添加调用完成。图 11.86 在工作表添加组框图 11.87 组合框中显示下拉列表〖语法补充〗:(1)执行本例中第一个过程后,再执行第二个过程可以得到相同结果,但如果执行第二个过程后就不能再执行第一个过程。因为对组合框的 ListFillRange 属性赋值后不允许再使用 AddItem 方法添加子项。但是可以利用以下语句清除 ListFillRange 属性,然后再添加子项:Sheets("Add2").ComboBox1.ListFillRange = ""(2)添加名称时,对名称命名需要遵循以下规则: 第一个字符不必须是英文字母或者汉字 不能在名称中使用空格、句点、惊叹号、或 @、&、$,# 等字符 名称的长度不可以超过 255 个字符 不能与 Function、Sub 过程名称相同 不能在范围的相同层次中使用重复的名称11.3.5设计三级下拉菜单〖案例要求〗:利用图 11.88 中的省名、市名、县名设计一个三级联动菜单。即单元格 A1 的省名变化时,B1 的市名相应变化,C1 县名也跟随 B1 的市名相应地变化。〖知识要点〗:Add〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 生成三级菜单()Dim cell As Range, 县名 As String, 市名 As String, Row_num As Integer, i, 省名'删除当前表所有名称For i = ActiveSheet.Names.Count To 1 Step -1ActiveSheet.Names(i).DeleteNext i'记录数据来源区的行数Row_num = Range("D1").CurrentRegion.Rows.Count'通过循环添加新的名称,包括市名和省名For Each cell In Range("E1:E" & Row_num)'如果单元格与其下一行相同Excel VBA 程序开发自学通2020-2-26第 342页 /共 508页IF cell.Offset(1, 0) = cell Then'如果循环到某个市名最后出现的位置,那么将变量县名赋值空文本IF WorksheetFunction.Match(cell, Range("E1:E" & Row_num), 0) = cell.RowThen 县名 = ""'将所有市名按逗号串连起来县名 = 县名 & """" & cell.Offset(0, 1) & """" & ","Else'否则添加名称,其 Name 为市名,引用源为所有县名与逗号、引号组成的字符串ActiveSheet.Names.Add Name:=cell.Text, RefersToR1C1:="={" & 县 名 &"""" & cell.Offset(0, 1) & """" & "}", Visible:=FalseEnd IF'如果循环到某个省名最后出现的位置,那么将变量市名赋值空文本IF WorksheetFunction.Match(cell.Offset(0, -1), Range("D1:D" & Row_num), 0) =cell.Row Then 市名 = ""'如果省名与其下一行的省名相同,而且市名与其与一行的县名不同IF cell.Offset(1, -1) = cell.Offset(0, -1) And cell cell.Offset(1, 0) Then'如果循环到某个省名最后出现的位置,那么将变量市名赋值空文本IF WorksheetFunction.Match(cell.Offset(0, -1), Range("D1:D" & Row_num),0) = cell.Row Then 市名 = ""'当前省中将所有市名串连起来市名 = 市名 & """" & cell & """" & ","Else'否则添加名称,Name 属性为省名,引用源为所有市名与逗号、引号组成的字符串ActiveSheet.Names.Add Name:=cell.Offset(0, -1).Text, RefersToR1C1:="={"& 市名 & """" & cell & """" & "}", Visible:=FalseEnd IF'将省名与逗号、引号组成字符串,使用其可以做为数据有效性的来源IF WorksheetFunction.Match(cell.Offset(0, -1), Range("D1:D" & Row_num), 0) =cell.Row Then 省名 = 省名 & cell.Offset(0, -1) & ","Next cell'设置默认值[a1:c1] = [d1:f1].Value'添加 A1 的数据有效性,来源等于变量“省名”With Range("A1").Validation.Delete.Add Type:=xlValidateList, Formula1:=省名.InCellDropdown = TrueEnd With'将名为 A1 字符的名称替换掉花括号做为 B1 的数据有效性来源With Range("B1").Validation.Delete.AddType:=xlValidateList,Formula1:=Replace(Replace(Replace(ActiveSheet.Names([A1].Text).RefersToR1C1, "={",""), """", ""), "}", "").InCellDropdown = TrueEnd With'将名为 C1 字符的名称替换掉花括号做为 C1 的数据有效性来源With Range("C1").Validation.Delete.AddType:=xlValidateList,Excel VBA 程序开发自学通2020-2-26第 343页 /共 508页Formula1:=Replace(Replace(Replace(ActiveSheet.Names([B1].Text).RefersToR1C1, "={",""), """", ""), "}", "").InCellDropdown = TrueEnd WithEnd Sub以上过程用于生成省、市、县相关的名称,并将 A1:C1 设置数据有效性。还需要配合工作表事件完成二级菜单与一级菜单的关联性以及三级菜单与二级菜单的关联性。(3)双击工程资源管理器中的工作表,进行工作表事件代码窗口,并输入以下事件过程代码:Private Sub Worksheet_Change(ByVal Target As Range)'如果当前单元格不是 A1:B1 则退出程序IF Target.Row > 1 Or Target.Column > 2 Then Exit Sub'如果当前单元格为空则退出程序IF Target(1) = "" Then Exit SubIF Target.Address = [A1].Address Then '如果是 A1'为 B1 修改数据有效性,随 A1 的变化而变化With Range("B1").Validation.Delete.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(ActiveSheet.Names([A1].Text).RefersToR1C1, "={", ""), """", ""), "}", "").InCellDropdown = True'设置默认选项Range("B1")=VBA.Split(Replace(Replace(Replace(ActiveSheet.Names([A1].Text).RefersToR1C1, "={",""), """", ""), "}", ""), ",")(0)End WithEnd IF'为 C1 修改数据有效性,随 B1 的变化而变化With Range("C1").Validation.Delete.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(ActiveSheet.Names([B1].Text).RefersToR1C1, "={", ""), """", ""), "}", "").InCellDropdown = True'设置默认选项Range("C1")=VBA.Split(Replace(Replace(Replace(ActiveSheet.Names([B1].Text).RefersToR1C1, "={",""), """", ""), "}", ""), ",")(0)End WithEnd Sub以上事件过程主要包含两项任务:设置 B1 和 C1 的默认值,以及 A1 变化时,修改 B1 单元格的有效性来源,B1 变化时修改 C1 单元格的有效性来源。(4)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成三级菜单”,工作表中 A1:C1 将产生默认的省市县名称;(5)单击选择 A1,从下拉列表中选择“四川”,那么 B1 单元格会相应地变化为“内江”,其下拉列表中也会产生与四川相关联的所有市名,C1 单元格相应地变化为“资中县”,见图 11.89 所示。如果从 B1 的下拉列表中选择“成都”,那么 C1 的默认值和下拉列表也会相对应变化为与成都相关联的所有县名。Excel VBA 程序开发自学通2020-2-26图 11.88 省市县数据源第 344页 /共 508页图 11.89 A1 修改时 B1 和 C1 相应变化〖语法补充〗:(1)本例中利用名称生成三级联动菜单限于名称的字符限制,当市、县名太多时会出错,主要由县名、市名与逗号串连后的字符串长度决定。11.4 Comments 对象应用案例Comments 对象即批注,在工作中常利用批注来对单元格的数据做说明,本节对批注在工作中的实际应用做详细介绍。11.4.1批量将数据导入批注〖案例要求〗:将用户指定的列的数据批注导入到选区的批注中,该数据来自当前列进行正数偏移或者负数偏移量的列中。〖知识要点〗:AddComment、ClearComments〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 导入批注()Dim rng As Range, Col As IntegerOn Error Resume Next '防错'如果选择对象不是单元格则退出程序IF TypeName(Selection) "Range" Then Exit Sub'如果选区列数大于 1 那么重新选择该区域的第一列IF Selection.Columns.Count > 1 ThenMsgBox "只能对一列进行操作.", 64, "提示"Selection(1).Resize(Selection.Rows.Count, 1).SelectExcel VBA 程序开发自学通2020-2-26第 345页 /共 508页End IFStar:'设置一个标签'让用户指定偏移列,以当前列为基准Col = Application.InputBox("请输入偏移列号" & Chr(10) & "程序将对当前列的偏移列单元格字符加入当前导列批注中", "指定导入批注的列", 1, , , , , 1)'如果输入数值超过 Integer 的有效范围则返回重新输入IF Err 0 Then Err.Clear: GoTo Star'输入的字符可以是负数也可以是正数,但按该值偏移后不能小于 A 列,不能大于已用区域的最后一列IFColRange([a1],ActiveSheet.UsedRange).Columns.Count - Selection.Column ThenMsgBox " 只 能 在 " & 1 - Selection.Column & " 到 " & Range([a1],ActiveSheet.UsedRange).Columns.Count - Selection.Column & "之间", 64, "提示"GoTo Star '返回重新输入End IFFor Each rng In Selection '遍历选择IF Len(rng.Offset(0, Col)) > 0 Then '只能非空单元格添加批注rng.ClearComments '先清除以前的批注rng.AddComment (rng.Offset(0, Col).Text) '添加新的批注End IFNext rngEnd Sub以上过程首先检查选择对象是否单元格,不是单元格则退出过程。同时检查选择的列数,因本工具是对选择的一列进行批量添加批注,如果用户选择多列的话,会自动重置选区为当前选区的第一列。然后让用户输入偏移量,可以是正数、0 和负数。当输入正数例如 3 时,表示将当前列向右偏移 3 列;当输入 0 时,表示当前列;当输入负数例如-5 时,表示向左偏移 5 列。这里的偏移量是有限制的,即偏移后不能小于 A 列而产生错误引用,也不能超出已用区域的最大列造成插入空白批注,所以必须借用 IF 和 Goto 语句让用户输入正确的范围值后再继续。(3)返回工作表,在工作表中选择 C4:D7,然后选择使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“导入批注”,那么程序会提示“只能对一列进行操作”,见图 11.90 所示;图 11.90选择多列时的提示(4)单击“确定”按钮后,程序会自动定位于选区的第一列,并弹出一个对话框,让用户录入偏移量,默认值为 1,见图 11.91 所示:Excel VBA 程序开发自学通2020-2-26图 11.91第 346页 /共 508页指定偏移量(5)如果输入 10,程序会产生图 11.92 所示的提示。其中-2 和 1 的计算方式是:当前列 3 减去最小列 1 等于 2已用区域最大值 4 减去当前列 3 等于 1单击“确定”按钮后程序会继续让用户输入偏移量,如果输入在-2 到 1 之外的任意值,程序会永远地循环下去,直到用户输入正确值。(6)输入偏移量 1,表示将第四列的部门加入到选区中,那么执行结果如图 11.93所示,每个单元格的批注都对应于它右边的部门。图 11.92输入错误值时提示正确范围图 11.93将部门导入到区域列的批注(7)如果选择 A2:A10,并输入偏移量为 3,那么所有姓名中导入的批注将会是他所在的部门,见图 11.94 所示;(8)也可以在空白列导入批注。例如选择 F2:F10 后输入 1,表示导入 G 列的数据,但是当前表已用区域为 A 列到 D 列,所以程序会弹出图 11.95 所示的提示,表示只能输入-5 到-2,用户只需要输入-5 到-2 之间的任意值即可。图 11.94将部门导入到姓名列的批注图 11.95允许范围〖语法补充〗:(1)AddComment 用于添加批注,它常与 ClearComments 同时使用。因为对已Excel VBA 程序开发自学通2020-2-26第 347页 /共 508页经有批注的单元格添加批注会产生错误而中断程序。(2)添加批注时必须使用文本,所以本例中使用“rng.Offset(0, Col).Text”而不用“rng.Offset(0, Col)”或者“rng.Offset(0, Col).value”,否则将引用区域中只有数值时无法批入批注。本节关于 Comments 对象的所有实例代码参见光盘:..\ 第十一章\ Comments.xlsm11.4.2在所有批注末尾添加指定日期〖案例要求〗:图 11.96 中有 4 个批注,现需对当前表的所有批注末尾添加日期,日期由用户随意指定。〖知识要点〗:Parent、Text〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 在批注中添加日期()Dim dat As String, aa As Comment'让用户指定一个日期,默认值为今日日期dat = Application.InputBox("请指定一个日期:", "日期", Date, , , , , 2)'遍历所有批注For Each aa In ActiveSheet.Comments'在批注后面添加日期aa.Parent.Comment.Text Text:=aa.Text & Chr(10) & datNextEnd Sub以上过程先首先让用户指定一个日期,默认值为当前系统日期。然后遍历所有批注,对批注的文字添加日期(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“在批注中添加日期”,程序会弹出一个对话框,让用户录入日期,默认值是当前系统日期,见图 11.97 所示:图 11.96成绩表图 11.97指定日期(4)如果使用默认日期,那么程序执行完成后,在当前表所有批注中将会添加日期,而且日期总会占据一个新行,见图 11.98 所示:(5)如果需要将日期添加到第一行,修改批注的代码可以修改为:aa.Parent.Comment.Text Text:=dat & ":" & Chr(10) & " " & aa.TextExcel VBA 程序开发自学通图 11.982020-2-26第 348页 /共 508页图 11.99将日期添加到批注后将日期添加到批注前〖语法补充〗:(1)Comments.Parent 属性返回指定对象的父对象,即存放批注的单元格。(2)Comment.text 方法用于设置批注文本。其语法如下:Comment.Text(Text, Start, Overwrite)三个参数含义如下:表 11-34名称TextStartOverwriteComment.text方法参数详解描述要添加的文本所添加文本的起始位置(字符数)。如果省略此参数,则删除批注中的所有现有文字如果为 True,则覆盖现有文件。默认值是 False(插入文本)利用第二参数和第三参数可以实现替换部分数据或者在中间插入部分字符。11.4.3为批注设置图片背景〖案例要求〗:在单元格插入一个图片批注,图片由用户随意选择。〖知识要点〗:Shape〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 插入图片标注()Dim Pict As String'先清除原有批注ActiveCell.ClearComments'让用户选择一张图片Pict = Application.GetOpenFilename("图片文件 (*.jpg; *.bmp),*.jpg; *.bmp")'如果未选择图片则退出IF Pict = "False" Then End'添加批注With ActiveCell.AddComment.Visible = False'隐藏批注.Shape.Fill.UserPicture Pict '填充图片.Shape.Height = 100#'高度为 100.Shape.Width = 120#'高度为 120End WithEnd Sub以上过程首先清除批注,然后由用户选择图片,最后将图片加入批注中,批注的大小可以调整。Excel VBA 程序开发自学通2020-2-26第 349页 /共 508页(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“插入图片标注”后如果当前单元格有批注,那么会清除原有的批注,然后弹出一个对话框由用户选择图片,支持 BMP 和 JPG 两种格式。最后产生的效果如图 11.100 所示:图 11.100插入图片批注〖语法补充〗:(1)Comment.Shape 属性返回一个 Shape 对象,它代表连接到指定批注的形状。(2)Shape 对象可使用 Fill 方法设置其填充颜色或者图片,图片可以使用任何有效的图片格式,但如果使用 GIF 动画,则不会产生动画效果。11.4.4添加个性化批注〖案例要求〗:添加外观更具个性的批注,提供多种形状可选。〖知识要点〗:Select〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 添加个性化批注()Dim mystr As String, mystr2 As String, Comment As Comment'清除原有批注ActiveCell.ClearComments'让用户输入批注内容,默认值为用户名mystr = InputBox("输入批注内容", "批注", Application.UserName, 10, 10)'让用户选择批注的形状,有 22 种可选项mystr2 = InputBox("输入批注外形" & Chr(10) & "1 口哨型,2 书卷型,3 箭头型,4 圆角矩形" & Chr(10) & "5 缺角矩形,6 菱型,7 五角星,8 云形标注,9 圆形,10 六边形,11 八边形,12 柱形,13笑脸形,14 心形,15 八角星,16 横卷形,17 竖卷形,18 波形,19 双波形,20 十六角星,21 二十四角星,22 文档.", "批注外型", 1, 10, 10)'添加批注Set Comment = ActiveCell.AddComment(mystr)With Comment.Visible = True '让批注可见.Shape.Select True '选择批注Select Case mystr2 '根据条件设置批注外观Case 1Selection.ShapeRange.AutoShapeType=msoShapeFlowchartSequentialAccessStorageCase 2Selection.ShapeRange.AutoShapeType = msoShapeFoldedCornerCase 3Excel VBA 程序开发自学通Case 42020-2-26第 350页 /共 508页Selection.ShapeRange.AutoShapeType = msoShapeRightArrowSelection.ShapeRange.AutoShapeTypemsoShapeRoundedRectangularCalloutCase 5Selection.ShapeRange.AutoShapeType = msoShapePlaqueCase 6Selection.ShapeRange.AutoShapeType = msoShapeDiamondCase 7Selection.ShapeRange.AutoShapeType = msoShape5pointStarCase 8Selection.ShapeRange.AutoShapeType = msoShapeCloudCalloutCase 9Selection.ShapeRange.AutoShapeType = msoShapeOvalCase 10Selection.ShapeRange.AutoShapeType = msoShapeHexagonCase 11Selection.ShapeRange.AutoShapeType = msoShapeOctagonCase 12Selection.ShapeRange.AutoShapeType = msoShapeCanCase 13Selection.ShapeRange.AutoShapeType = msoShapeSmileyFaceCase 14Selection.ShapeRange.AutoShapeType = msoShapeHeartCase 15Selection.ShapeRange.AutoShapeType = msoShape8pointStarCase 16Selection.ShapeRange.AutoShapeType = msoShapeHorizontalScrollCase 17Selection.ShapeRange.AutoShapeType = msoShapeVerticalScrollCase 18Selection.ShapeRange.AutoShapeType = msoShapeWaveCase 19Selection.ShapeRange.AutoShapeType = msoShapeDoubleWaveCase 20Selection.ShapeRange.AutoShapeType = msoShape16pointStarCase 21Selection.ShapeRange.AutoShapeType = msoShape24pointStarCase 22Selection.ShapeRange.AutoShapeType = msoShapeFlowchartDocumentEnd Select.Visible = False '隐藏批注End WithActiveCell.Select '返回单元格End Sub=以上过程首先清除原有批注,然后让用户录入批注的内容,并选择批注外观的形状。外观由数字 1 到 22 范围内的数值决定。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加个性化批注”然后程序会弹出一个对话框,让用户输入批注的内容,默认值为 Office安装用户名,见图 11.101 所示:Excel VBA 程序开发自学通2020-2-26图 11.101第 351页 /共 508页提示录入批注内容(4)在对话框中录入批注内容“明天休息”,并单击“确定”,程序会继续弹出“批注外型”的对话框,由用户在 22 种形状中选择,默认值为 1,见图 11.102 所示:图 11.102选择批注的形状(6)输入 16,表示横卷形,然后单击“确定”按钮,活动单元格将产生图 11.103所示批注;如果输入的 6,表示使用菱形,那么产生的结果见图 11.104 所示:图 11.103横卷形批注图 11.104菱形批注(7)本例中使用了 Select Case 条件语句,代码较长。鉴于变量 Mystr2 的规律性,可以使用 Choose 来简化过程。代码如下:Sub 添加个性化批注 2()Dim mystr As String, mystr2 As String, Comment As Comment'清除原有批注ActiveCell.ClearCommentsmystr = InputBox("输入批注内容", "批注", Application.UserName, 10, 10)mystr2 = InputBox("输入批注外形" & Chr(10) & "1 口哨型,2 书卷型,3 箭头型,4 圆角矩形" & Chr(10) _& "5 缺角矩形,6 菱型,7 五角星,8 云形标注,9 圆形,10 六边形,11 八边形,12 柱形,13 笑脸形,14 心形,15 八角星," _& "16 横卷形,17 竖卷形,18 波形,19 双波形,20 十六角星,21 二十四角星,22 文档.", "批注外型", 1, 10, 10)Set Comment = ActiveCell.AddComment(mystr)With Comment.Visible = True.Shape.Select TrueSelection.ShapeRange.AutoShapeType=Choose(mystr2,msoShapeFlowchartSequentialAccessStorage, _Excel VBA 程序开发自学通2020-2-26第 352页 /共 508页msoShapeFoldedCorner,msoShapeRightArrow,msoShapeRoundedRectangularCallout, msoShapePlaque, _msoShapeDiamond,msoShape5pointStar,msoShapeCloudCallout,msoShapeOval, msoShapeHexagon, _msoShapeOctagon, msoShapeCan, msoShapeSmileyFace, msoShapeHeart,msoShape8pointStar, _msoShapeHorizontalScroll,msoShapeVerticalScroll,msoShapeWave,msoShapeDoubleWave, _msoShape16pointStar, msoShape24pointStar, msoShapeFlowchartDocument).Visible = FalseEnd WithActiveCell.SelectEnd Sub〖语法补充〗:(1)Shape.Select 方法用于选择对象,通常指图形对象。它的语法如下:Shape.Select(Replace)参数 Replace 表示用指定的对象替换当前所选内容,如果参数为 False 则可以同时选择多个对象。例如选择当前表所有图形对象,那么可以使用以下过程,如果其中参数 False 改用 True 则只能选择最后一个对象。Sub 选择所有图形对象()On Error Resume NextDim a As ShapeFor Each a In ActiveSheet.Shapesa.Select (False)Next aEnd Sub11.4.5批量修改当前表批注的外观〖案例要求〗:将当前表所有批注修改外观。〖知识要点〗:ShapeRange 、AutoSize〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 批量修改批注外观()Dim Comment As Comment, Style As ByteOn Error Resume Next'防错Star:'设置标签'让用户选择批注形状Style = InputBox("输入批注外形" & Chr(10) & "1 口哨型,2 书卷型,3 箭头型,4 圆角矩形" & Chr(10) _& "5 缺角矩形,6 菱型,7 五角星,8 云形标注,9 圆形,10 六边形,11 八边形,12 柱形,13 笑脸形,14 心形,15 八角星," _& "16 横卷形,17 竖卷形,18 波形,19 双波形,20 十六角星,21 二十四角星,22 文档.", "批注外型", 1, 10, 10)'如果有错误则返回重新输入IF Err 0 Then err.Clear: GoTo Star'如果值不在 1 到 22 这个范围则提示用户正确范围,并返回IF Style 22 Then MsgBox "只能在 1 到 22 之间", 64, "提示": GoTo StarExcel VBA 程序开发自学通2020-2-26第 353页 /共 508页'遍历所有批注For Each Comment In ActiveSheet.CommentsWith Comment.Visible = True '设置为可见.Shape.Select True '选择批注'设置外观Selection.ShapeRange.AutoShapeType = Choose(Style, msoShapeFlowchartSequentialAccessStorage, _msoShapeFoldedCorner, msoShapeRightArrow, msoShapeRoundedRectangularCallout, msoShapePlaque, _msoShapeDiamond, msoShape5pointStar, msoShapeCloudCallout, msoShapeOval, msoShapeHexagon, _msoShapeOctagon, msoShapeCan, msoShapeSmileyFace, msoShapeHeart, msoShape8pointStar, _msoShapeHorizontalScroll, msoShapeVerticalScroll, msoShapeWave, msoShapeDoubleWave, _msoShape16pointStar, msoShape24pointStar, msoShapeFlowchartDocument)'自动适应大小Selection.AutoSize = True.Visible = False '隐藏批注End WithNextEnd Sub以上过程利用 For 循环遍历所有批注,并逐个修改批注外观。在选择外观时,有必要进行防错,否则用户输入 0 等等值会产生错误。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量修改批注外观”,程序会弹出图 11.105 所示对话框,让用户从 22 种外观中选择一种,默认为第一种。执行结果见图 11.106 所示:图 11.105选择外观图 11.106修改的批注〖语法补充〗:(1)Comment.Shape 属性返回一个 Shape 对象,它代表连接到指定批注的形状。通过 AutoShapeType 属性可以设置 Shape 对象的外观。(2)修改外观后,必须使用 AutoSize 调整其大小,否则部分文字可能看不到。11.4.6替换所有批注中的“计算机”为“电脑”〖案例要求〗:将当前工作簿中所有工作表的批注中的“计算机”替换为“电脑”。〖知识要点〗:Text〖实现步骤〗:Excel VBA 程序开发自学通2020-2-26第 354页 /共 508页(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 替换批注中的计算机为电脑()Dim Comment As Comment, Sht As Worksheet'遍历所有工作表For Each Sht In Sheets'遍历所有批注For Each Comment In Sht.Comments'替换批注中的字符串Comment.text text:=Replace(Comment.text, "计算机", "电脑")Next CommentNext ShtEnd Sub以上过程利用循环嵌套方式遍历工作簿中所有批注,并逐一替换批注的字符。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“替换批注中的计算机为电脑”,那么工作簿中所有批注都会在瞬间完成字符替换。图11.107 和图 11.108 是替换前后的批注对比。图 11.107替换批注字符前图 11.108替换字符替换后〖语法补充〗:(1)Replace 函数可以将批注中的指定字符串替换成其它字符串,也可以删除指定的字符串,只需要 Replace 的第三参数使用空文本即可。11.5 Sheets 对象应用案例Sheets 对象集合是所有表对象的集合,它包括工作表和图表,但在 VBA 中与用户打交道的通常是工作表,设计图表时极少借用 VBA 来完成。本节对工作表的应用进行一些案例讲解。11.5.1添加汇总工作表〖案例要求〗:在工作簿中添加新表,名为“汇总”,且新工作表位于最后一个工作表。〖知识要点〗:Add〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Excel VBA 程序开发自学通2020-2-26第 355页 /共 508页Sub 新建工作表()'在最后一个工作表之后新建一个工作表,且为“汇总”Dim sht As WorksheetOn Error Resume NextSet sht = Sheets("汇总")IF Err = 0 Then Exit SubSheets.Add(after:=Sheets(Sheets.Count)).Name = "汇总"End Sub以上过程在创建工作表前需要判断工作簿中是否存在同名工作表,否则程序会弹出错误对话框、中途中断程序,而且产生一个未命名的新建工作表。判断“汇总”工作表是否存在的方式是将该工作表对象赋值给对象变量,如果不产生错误则表示当前工作簿中有“汇总”表存在,否则不存在。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建工作表”,在工作簿中职果不存在“汇总”表将产生一个名为“汇总”的新表,而且该表总位于最末尾。〖语法补充〗:(1)Sheets.Add 方法用于新建工作表、图表或者宏表,新建的工作表将成为活动工作表。它的语法如下:Sheets.Add(Before, After, Count, Type)四个参数都是可选参数。如果忽略所有参数,表示在当前活动工作表之前添加一个新工作表。各参数含义如下:表 11-35名称BeforeAfterCountTypeSheets.Add方法参数详解描述指定工作表的对象,新建的工作表将置于此工作表之前指定工作表的对象,新建的工作表将置于此工作表之后要添加的工作表数。默认值为 1指定工作表类型。可以为下列XlSheetType常量之一:xlWorksheet、xlChart、xlExcel4MacroSheet或xlExcel4IntlMacroSheet。如果基于现有模板插入工作表,则指定该模板的路径。默认值为 xlWorksheet根据上表分析,如果需要插入一个新表,位于最前面位置,那么可以使用以下语句:Sheets.Add Sheets(1)如果需要插入的新表位于第二个工作表之后,则用以下语句:Sheets.Add , Sheets(2)——第一参数只需要保留逗号即可Sheets.Add After:=Sheets(2)——完全忽略第一参数,直接对第二参数赋值,但必须注明 After如果需要在末尾新建三个图表,则用以下语句:Sheets.Add , Sheets(Sheets.Count), 3, xlChart本例文件参见光盘:..\ 第十一章\新建汇总工作表.xlsm11.5.2批量添加工作表且以本月日期命名〖案例要求〗:以本月的每日日期为名批量创建工作表。〖知识要点〗:Name〖实现步骤〗:Excel VBA 程序开发自学通2020-2-26第 356页 /共 508页(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 批量创建日期工作表()'如果当前表工作表数据已经大于等于本月天数则退出程序IF Sheets.Count >= Day(WorksheetFunction.EoMonth(Date, 0)) Then Exit Sub'批量添加工作表,个数等于本月天数与现在工作表数量之差Sheets.Add , , Day(WorksheetFunction.EoMonth(Date, 0)) - Sheets.CountDim sht As Worksheet, Item As Byte'遍历所有工作表For Each sht In Sheets'累加计数器Item = Item + 1'逐个工作表命名,等于本月每一天的日期sht.Name = Format(WorksheetFunction.EoMonth(Date, -1) + Item, "mm 月 dd 日")Next shtEnd Sub以上过程在创建工作表之前判断工作表个数,如果大于等于本月天数则退出程序。然后批量创建工作表,使其个数与现在工作表之和等于本月天数。最后利用 For 循环逐个进行工作表命名。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量创建日期工作表”,将瞬间达成批量创建工作表的需求,且每个工作表皆按日期命名(测试月为五月),结果见图 11.109 所示:图 11.109批量创建本月每日工作表〖语法补充〗:(1)Worksheet.Name 属性表示表名称。可以直接通过等号对它赋值。赋值时尽量使用文本格式,如果是日期可能会被 Excel 转换为不可预料的格式,受控制面板格式的影响。本例文件参见光盘:..\ 第十一章\以日期批量创建工作表.xlsm11.5.3迅速产生样表〖案例要求〗:将“样表”工作表复制一分,并以今天之日期命名,从而避免每天设计表格的格式。样表见图 11.110 所示。〖知识要点〗:Copy〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 复制样表()Excel VBA 程序开发自学通2020-2-26第 357页 /共 508页'声明一个工作表对象Dim sht As Worksheet'防错On Error Resume Next'将名称等于当前日期的工作表赋与变量Set sht = Sheets(CStr(Date))'如果在错误,则表示不存在该表,那么将“样表”复到最后,且取名为今日日期IF Err 0 ThenSheets("样表").Copy after:=Sheets(Sheets.Count)Sheets(Sheets.Count).Name = DateEnd IFEnd Sub以上过程因复制工作表后需要命名,那么必须检查是否存在同名工作表。所以在过程中使用了防错机制配合工作表对象变量查核错误值,如果错误值大于 0 则复制样表,否则表式已经存在。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“复制样表”,那么在工作表标签的最末处将产生复制一份样表,其格式与预设的样本完成一致,只需要录入数据即可,而表格的名称为今日日期,见图 11.111 所示:图 11.110 样表图 11.111 复制样表并以日期命名(4)本例代码也可以利用第 10 章的错误处理函数来完成,特别是需要多处使用工作表命名时。调用错误处理函数的方式来完成本例需求,代码如下:Function IsSht(ShtName As String) As BooleanOn Error Resume NextDim sht As WorksheetSet sht = Sheets(ShtName)IsSht = (Err = 0)End FunctionSub 复制样表 2()IF Not IsSht(CStr(Date)) ThenSheets("样表").Copy after:=Sheets(Sheets.Count)Sheets(Sheets.Count).Name = DateEnd IFEnd Sub〖语法补充〗:(1)Worksheets.Copy 方法用于将工作表复制到工作簿的另一位置。具体语法如下:Worksheets.Copy(Before, After)第一参数 Before 表示将要在其之前放置工作表副本的工作表。如果指定了 After,Excel VBA 程序开发自学通2020-2-26第 358页 /共 508页则不能指定 Before。第二参数表示将要在其之后放置工作表副本的工作表。(2)如果 Sheets.Copy 方法既不指定 Before 参数也不指定 After 参数,则 Excel将新建一个工作簿,其中包含复制的工作表。本例文件参见光盘:..\ 第十一章\复制样表.xlsm11.5.4将当前表移到其基它工作簿〖案例要求〗:将当前工作表移到“人事资料.xlsx”中。〖知识要点〗:Move〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Function IsWkb(Wkb As String) As Boolean'防错On Error Resume Next'声明一个工作簿对象Dim Wkbs As Workbook'将名为 wkb 的工作簿赋与对象变量 Wkbs,'如果存在名为 Wkb 的工作簿则不会出错,否则会出错Set Wkbs = Workbooks(Wkb)'函数的结果由是否存在错误来决定,没有错误则表示已有 Wkb 工作簿IsWkb = (Err = 0)End Function以上函数用于判断目标工作簿是否存在。Sub 移动工作表()'如果已经打开“人事资料.xlsx”IF IsWkb("人事资料.xlsx") Then'将当前表移到工作簿“人事资料.xlsx”最末尾位置ActiveSheet.Move after:=Workbooks("人事资料.xlsx").Sheets(Sheets.Count)End IFEnd Sub以上过程首先判断是否已打开“人事资料.xlsx”,如果 True 则移动工作表。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“移动工作表”,那么当前工作表会移到“人事资料.xlsx”工作簿的工作表标签末尾位置。〖语法补充〗:(1)Worksheets.Move 方法用于将工作表移到工作簿中的其他位置。它的语法如下:Worksheets.Move(Before, After)第一参数 Before 表示在其之前放置移动工作表的工作表,可以是本工作簿的工作表,也可以是其它工作簿的工作表。如果是其它工作簿的工作表,那么需要将工作簿名完整地指定,但不需要路径。如果指定了 After 参数,则不能指定 Before 参数。(2)移动工作表后,VBA 会激活目标工作簿,同时该工作表也处于激活状态。(3)如果既不指定 Before 也不指定 After,Microsoft Excel 将新建一个工作簿,其中包含所移动的工作表。本例文件参见光盘:..\ 第十一章\移动工作表.xlsmExcel VBA 程序开发自学通11.5.52020-2-26第 359页 /共 508页除“目录”工作表外隐藏其它所有工作表〖案例要求〗:除“目录”工作表外隐藏其它所有工作表。〖知识要点〗:Visible〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 隐藏目录以外的所有工作表()'声明对象变量Dim sht As Worksheet'遍历工作表For Each sht In Sheets'如果工作表名称不等于“目录”则隐藏IF sht.Name " 目 录 " Then sht.Visible = xlSheetVeryHidden Else sht.Visible =xlSheetVisibleNext shtEnd Sub以上过程使用 For 循环遍历所有工作表,然后检查工作表名字是否“目录”,对于“目录”以外的所有工作表进行深度隐藏。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏目录以外的所有工作表”,当前工作簿中“目录”以外的工作表全部隐藏。〖语法补充〗:(1)Worksheet.Visible 属性可以返回或设置可工作表的可见性。它有三种状态可选:表 11-36 工作表可见性常量详解常量xlSheetHidden值0xlSheetVeryHidden2xlSheetVisible-1含义隐藏工作表,用户可以通过菜单取消隐藏隐藏对象,以便使对象重新可见的唯一方法是将此属性设置为 True(用户无法使该对象可见)显示工作表(2)本例中设置“目录”以外的所有工作表为 xlSheetVeryHidden,表示深度隐藏,无法通过工作表标签中的【取消隐藏】菜单来设置该工作表的隐藏属性。本例文件参见光盘:..\ 第十一章\隐藏目录以外的工作表.xlsm11.5.6分别计算工作表数量和图表数量〖案例要求〗:分别计算工作表数量和图表数量。〖知识要点〗:Count〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 统计工作表与图表个数()MsgBox "当前工作簿中有:" & Chr(10) & Chr(10) & _"工作表:" & Worksheets.Count & "个" & Chr(10) & _Excel VBA 程序开发自学通2020-2-26第 360页 /共 508页"图 表:" & Charts.Count & "个"End Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“统计工作表与图表个数”,程序会弹出图 11.112 所示对话框,,表示工作表个数和图表个数。图 11.112 统计工作表和图表个数〖语法补充〗:(1)Worksheets.Count 属性代表工作表集合中对象的数量,仅仅代表工作表的数量。(2)Charts.Count 属性代表图表集合中对象的数量,仅仅代表图片表的数量。Charts 加上 Worksheets 组合成 Sheets 对象集合。本例文件参见光盘:..\ 第十一章\计算工作表与图表数量.xlsm11.5.7建立带链接功能工作表目录且通过快捷键返回目录〖案例要求〗:建立带链接功能的工作表目录且通过快捷键返回目录。〖知识要点〗:Hyperlinks〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 创建目录()'关闭屏幕更新加快速度Application.ScreenUpdating = FalseDim i As Integer, Sht_Count'如果不存在“目录”则添加目录工作表IF Not IsSht("目录") Then Sheets.Add(Sheets(1)).Name = "目录"Sht_Count = Sheets.Count'获取工作表数量For i = 2 To Sht_Count'遍历工作表'在“目录”工作表添加链接Sheets("目录").Hyperlinks.Add Anchor:=Sheets("目录").Cells(i - 1, 2), Address:="",SubAddress:="'" & Sheets(i).Name & "'!A1", TextToDisplay:=Sheets(i).Name, ScreenTip:="单击打开:" & Sheets(i).NameNext i'恢复屏幕更新Application.ScreenUpdating = TrueExcel VBA 程序开发自学通2020-2-26第 361页 /共 508页'为返回目录过程指定快捷键为【Ctrl+J】Application.OnKey "^j", "返回目录"End Sub'声明一个函数,用于判断是否存在某个指定名称的工作表Function IsSht(ShtName As String) As BooleanOn Error Resume NextDim sht As WorksheetSet sht = Sheets(ShtName)IsSht = (Err = 0)End FunctionSub 返回目录()'如果有“目录”则返回“目录”工作表IF IsSht("目录") Then Sheets("目录").SelectEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“创建目录”,在工作簿中将添加一个新工作表,表中 B 列存放当前工作簿所有工作表的目录,当鼠标箭头指定目录时,将提示单击打开某工作表,见图 11.113 所示:图 11.113 创建带链接的工作表目录(4)单击 B4 单元格,程序会自动跳转到工作表“周四”;(5)按下快捷键【Ctrl+J】返回“目录”工作表。〖语法补充〗:(1)Worksheets.Hyperlinks.Add 方法可向指定的区域加超链接。它的语法如下:Worksheets.HyperlinksAdd(Anchor,Address,SubAddress,ScreenTip,TextToDisplay)其中五个参数的含义如下:表 11-37 Hyperlinks Add参数详解AnchorAddressSubAddress必选/可选必选必选可选ScreenTip可选TextToDisplay可选名称描述超链接的位置。可为 Range 或 Shape 对象超链接的地址超链接的子地址当鼠标指针停留在超链接上时所显示的屏幕提示要显示的超链接的文本(2)也可以通过 Hyperlinks.Add 方法添加网址链接。例如添加一个打开 163 新闻的链接,代参天如下:Hyperlinks.Add Anchor:=[a1], Address:="http://news.163.com", TextToDisplay:="163新闻"Excel VBA 程序开发自学通2020-2-26第 362页 /共 508页以上代码表示在单元格中显示“163 新闻”,而链接网址为 http://news.163.com。( 3 ) 也 可 以 利 用 Hyperlinks.Add 方 法 添 加 邮 件 地 址 链 接 。 例 如 单 击 后 向Andy_qc@163.com 发送邮件,那么代码如下:Hyperlinks.AddAnchor:=[a1],Address:="mailto:andy_qc@163.com",TextToDisplay:="发邮件"以上代码表示单元格中显示“发邮件”,单击后可以打开发邮件的窗口,且收件人是“andy_qc@163.com”。本例文件参见光盘:..\ 第十一章\创建工作表目录.xlsm11.5.8对当前表已用区域设置背景图片〖案例要求〗:对当前表已用区或设置一幅背景图片,而其它区域则不产生图片。〖知识要点〗:SetBackgroundPicture〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 为已区用域设定背景图片()Dim Pic As String, rng As Range'弹出对话框,让用户选择一张做为背景的图片Pic = Application.GetOpenFilename("图片文件 (*.jpg; *.bmp),*.jpg; *.bmp")'如果未选择图片则退出IF Pic = "False" Then End'为当前表设置背景图片ActiveSheet.SetBackgroundPicture Filename:=PicSet rng = ActiveSheet.UsedRange'将多余的列设置为白色Range(Cells(1, Range([a1], rng).Columns.Count + 1), Cells(1, Columns.Count)).EntireColumn.Interior.ColorIndex = 2'将多余的行设置为白色Rows((Range([a1],rng).Rows.Count+1)&":"&Rows.Count).EntireRow.Interior.ColorIndex = 2End Sub以上过程首先让用户选择一张图片,可以是 Jpg 或者 Bmp 两种格式之一。然后利用 etBackgroundPicture 方法将图片设置为工作表的背景,且将已用区域以外的区域填充白色,从而实现只能在已用区域看到图片背景。过程中使用“Range([a1], rng) Columns.Count”来获取已用区域的列数,而不用Rng. Columns.Count 是为了确保代码的通用性。因为用户的已用区域不一定多 A1 开始,可能 A 列或者第 1 行不属于已用区域,在此情况下会差生误差,将有数据的列或者行也设置白色背景。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“为已区用域设定背景图片”,程序会弹出一个对话框让用户选择图片。当选择图片后在工作表的已用区域将产生图片背景,而其它区域则不会有图片背景,见图 11.114 所示:Excel VBA 程序开发自学通2020-2-26第 363页 /共 508页图 11.114 仅对已用区或添加背景色〖语法补充〗:(1)Worksheet.SetBackgroundPicture 方法用于为工作表设置背景图形。它的语法如下:Worksheet..SetBackgroundPicture(Filename)其中参数 Filename 表示图片的完整路径,可以使用的格式很多,但若使用 GIF动画却无法产生动画效果。(2)对已用区域之外的区域设置白色背景也可以改用以下方法:Range(Columns(Columns.Count), Columns(Columns.Count).End(xlToLeft).Offset(0, 1)).Interior.ColorIndex = 2Range(Rows(Rows.Count),Rows(Rows.Count).End(xlUp).Offset(1,0)).Interior.ColorIndex = 2本例文件参见光盘:..\ 第十一章\设置背景图片.xlsm11.5.9批量命名工作表〖案例要求〗:根据单元格的值对工作表进行批量命名。〖知识要点〗:Name〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 批量改名()Dim sht As Worksheet, Item As Integer'如果单元格个数不等于非空单元格个数则退出程序(也就是确保没有空单元格)IF [a1:a5].Count WorksheetFunction.CountA([a1:a5]) Then Exit Sub'首先判断区域中是否存在重复值,如果不重复数据个数等于单元格个数,那么才执行命名'计算单元格中不重复数据个数(所有单元格都非空)的方法是使用数组公式,Evaluate直接将数组公式求值IFApplication.Evaluate("=SUMPRODUCT(1/COUNTIF(A1:A5,A1:A5))")=[a1:a5].Count Then'遍历工作表For Each sht In SheetsExcel VBA 程序开发自学通2020-2-26第 364页 /共 508页Item = Item + 1'改名,将单元格的值导入到工作表名称sht.Name = Cells(Item, 1)Next shtEnd IFEnd Sub以上过程首先判断区域中是否存在空单元格,然后判断是否存在重复值,只有区域中所有单元格非空且不重复值也可以执行过程,否则命名时会产生错误。在计算区域中不重复值个数时调用了数组公式,读者可以将这种思路用到其它类似的场合。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量改名”,工作簿中所有工作表都瞬间完成重命名,见图 11.115 所示:图 11.115 从区域中获取数据对工作表批量命名〖语法补充〗:(1)Evaluate 的参数可以使用名称,也可以使用公式。在本例中使用了一个公式,将该公式直接录入到单元格也可以正确运行。而且不用等号 Evaluate 也可以计算出结果。(2)也可以利用方括号“[]”来替代 Evaluate,例如使用以下代码也可以正确获得结果 5:MsgBox [=SUMPRODUCT(1/COUNTIF(A1:A5,A1:A5))]本例文件参见光盘:..\ 第十一章\工作表批量命名.xlsm11.5.10隐藏所有工作表非使用区〖案例要求〗:将所有工作表的非使用区域都隐藏起来。〖知识要点〗:UsedRange〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 隐藏非使用区()Dim Rownum As Long, ColNum As Long, cell As Range, j As Long, k As Long, Sht AsWorksheet, ans As ByteDim Col As Long, Row As LongOn Error Resume Next'记录最大行与最大列(通用于 Excel 2003 和 2007)Row = Rows.CountCol = Columns.CountStar:Excel VBA 程序开发自学通2020-2-26第 365页 /共 508页'让用户选择隐藏还是取消隐藏ans = Application.InputBox(" 输 入 1: 隐 藏本 工 作 簿 中所 有 工 作 表的 非 使 用 区域 " &Chr(10) & "输入 2:取消所有工作表隐藏区", "请选择执行方式", 2, , , , , 1)IF Err 0 Then GoTo StarIF ans 1 And ans 2 Then MsgBox "输入错误,请输入 1 或者 2", 65, "友情提示":GoTo StarIF ans = 1 Then GoTo 隐藏还原:Application.ScreenUpdating = FalseFor Each Sht In Worksheets'在所有表中循环,忽略图表Sht.Cells.EntireRow.Hidden = False'取消隐藏Sht.Cells.EntireColumn.Hidden = FalseNext ShtApplication.ScreenUpdating = TrueExit Sub隐藏:On Error GoTo errApplication.ScreenUpdating = FalseFor Each Sht In ActiveWorkbook.Worksheets '遍历所有工作表(忽略图表)'如果工作表中没有数据则运行 black 标签处的语句IF WorksheetFunction.CountA(Sht.Cells) = 0 Then GoSub black'记录已用区域行数和列数Rownum = Sht.Range(Sht.[a1], Sht.UsedRange).Rows.CountColNum = Sht.Range(Sht.[a1], Sht.UsedRange).Columns.Countj=1'通过循环获取已用区域最后一行的行号(取每列的最大值)For Each cell In Sht.Range(Sht.Range("a" & Row), Sht.Cells(Row, ColNum))IF j < cell.End(xlUp).Row Then j = cell.End(xlUp).RowNextk=1'记录已用区域最后一列的列号For Each cell In Sht.Range(Sht.Cells(1, Col), Sht.Cells(Rownum, Col))IF k < cell.End(xlToLeft).Column Then k = cell.End(xlToLeft).ColumnNext'隐藏已用区域以外的所有区域Sht.Range(Sht.Cells(1, k + 1), Sht.Cells(1, Col)).EntireColumn.Hidden = TrueSht.Range(Sht.Cells(j + 1, 1), Sht.Cells(Row, 1)).EntireRow.Hidden = TrueNext ShtApplication.ScreenUpdating = TrueExit Subblack:'将所有行列都隐藏Sht.Cells.EntireColumn.Hidden = TrueSht.Cells.EntireRow.Hidden = TrueReturnerr:On Error Resume Next'当有图形对象覆盖空或者空列时,该行、列隐藏将出错MsgBox "不能将对象移至工作表以外的区域。" & Chr(10) & "【" & Sht.Name & "】 操作不成功!" _& Chr(10) & "原因有两个:可能有图形对象位于工作表的空白区;" & Chr(10) & _"也可能最后一行或列有批注占用了工作表的空白区。", 65, "友情提示"Excel VBA 程序开发自学通2020-2-26第 366页 /共 508页Resume NextEnd Sub以上过程首先弹出一个对话框由用户选择隐藏还是取消藏,然后根据输入值决定执行方式。在隐藏非使用区域时利用 Range.End 方法来判断使用区域的最大行和最大列,这种方相对于定位或者 Usedrange 方法效率上更低一些,然而当用户的工作表中有大量带格式的空单元格时,本方法就会体现出优势了——准确度更高;而在取消所有工作表的隐藏区域时则不需要任何判断,直接对所有行、所有列的 Hidden 属性赋值即可。隐藏行与列时,需要注意两点:图表不存在行与列,所以必须在工作表中循环,忽略图表;如果工作表中有任意图形对象、批注、嵌入式图表在已用区域之外,那么该表即无法完成隐藏非使用区,因为 Excel 无法让图形对象处可见行、列之外。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏非使用区”,程序会弹出一个对话框,让用户选择执行方式,默认值为 2,见图 11.116所示。(4)如果输入值不等于 1 也不等于 2,那么程序会返回对话框要求继续输入,直到输入值在允许范围为止。如果输入数值 1,那么程序会将所有工作表中非使用区域隐藏起来,仅能看到有数据的行与列,见图 11.117 所示:图 11.116选择执行方式图 11.117隐藏非使用区后〖语法补充〗:(1)Worksheet.UsedRange 属性返回一个 Range 对象,该对象表示指定工作表上所使用的区域。已用区域是以单元格是否存在数据和格式为依据。如果单元格有数据或者无数据但保留了用户设置的格式信息,包括字体色、背景色、字体加粗等等,那么该单元格就属于已用区域。(2)工作表才有 UsedRange 属性,图表不存在 UsedRange 属性。本例文件参见光盘:..\ 第十一章\隐藏所有工作表非使用区.xlsm11.6 Workbooks 对象应用案例Workbooks 对象集合包括包有工作簿,工作簿是一个单独的文档,保存完整的报表信息,不像工作表或单元格一样附属于其它对象中。本节对工作簿对象进行详细地实例演示。Excel VBA 程序开发自学通11.6.12020-2-26第 367页 /共 508页新建工作簿且对其命名为今日期〖案例要求〗:新建一个工作簿,且另存为当前系统日期,保存路径由用户选择。〖知识要点〗:Add、〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 新建工作表簿以日期命名()'新建一个工作簿Workbooks.Add'发送命令,内容为当前日期,发送到下一句代码所打开的对话框中Application.SendKeys Format(Date, "yyyy-mm-dd")'打开另存为对话框Application.Dialogs(5).ShowEnd Sub以上过程首先建立一个工作簿,然后打开“另存为”对话框,且发送当前日期到对话框,成为默认日期名称。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建工作表簿以日期命名”,那么程序会立即新建一个工作簿,且打开图 11.118 所示“另外为”对话框,让用户选择保存路径及文件名,默认文件名为当前系统日期。图 11.118 以日期为默认名保存文件〖语法补充〗:(1)Workbooks.Add 方法新建一个工作簿,新工作簿将成为活动工作簿。它的语法如下:Workbooks.Add(Template)(2)Workbooks.Add 方法的参数 Template 确定如何创建新工作簿。如果此参数为指定现有 Excel 文件名的字符串,那么创建新工作簿将以该指定的文件作为模板。如果此参数为常量,新工作簿将包含一个指定类型的工作表。可为以下Excel VBA 程序开发自学通2020-2-26第 368页 /共 508页XlWBATemplate 常 量 之 一 : xlWBATChart 、 xlWBATExcel4IntlMacroSheet 、xlWBATExcel4MacroSheet 或 xlWBATWorksheet。例如新建一个仅仅包括一个图表的工作簿,那么可以使用以下语句:Workbooks.Add xlWBATChart新建一个仅包括一个宏表的工作簿,那么可以使用以下语句:Workbooks.Add xlWBATExcel4IntlMacroSheet如果当前已打开一个名为“预算.xls”或者“预算.xlsx”的工作簿模板文件,那么以下语法可以建立一个以该文件为模板,且名为“预算 1”的工作簿:Workbooks.Add "预算"本例文件参见光盘:..\ 第十一章\新建工作簿另存为日期.xlsm11.6.2将当前工作簿另存且加密为 123〖案例要求〗:将当前工作簿另存到 C 盘,且文件打开密码为 123。〖知识要点〗:SaveAs〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 另存且密码为 123()ActiveWorkbook.SaveAs Filename:="C:\工作簿 123.xlsm", Password:=123, FileFormat:=xlOpenXMLWorkbookMacroEnabledEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“另存且密码为 123”,那么当前工作簿会另存在 C:\工作簿 123.xlsm,且打开时该文件时会提示密码。(4)如果使用 Excel 2003,那么不支持 xlsm 格式的文件,必须改用以下代码:Sub 另存且密码为 123()ActiveWorkbook.SaveAs Filename:="C:\工作簿 123.xls", Password:=123End Sub( 5 ) 如 果 需 要 代 码 通 用 于 Excel 2003 和 2007 , 那 么 需 求 通 过 IF 配 合Application.Version 来完成。代码如下:Sub 另存且密码为 123()IF Application.Version * 1 = 12 ThenActiveWorkbook.SaveAs Filename:="C:\工作簿 123.xlsm", Password:=123, FileFormat:=xlOpenXMLWorkbookMacroEnabledElseActiveWorkbook.SaveAs Filename:="C:\工作簿 123.xls", Password:=123End IFEnd Sub〖语法补充〗:(1)Workbook.SaveAs 方法用于另存工作簿,在另一不同文件中保存对工作簿所做的更改。它的语法如下:Workbook.SaveAs(FileName,FileFormat,Password,WriteResPassword,ReadOnlyRecommended, CreateBackup, AccessMode, ConflictResolution, AddToMru,TextCodepage, TextVisualLayout, Local)Excel VBA 程序开发自学通2020-2-26第 369页 /共 508页表 11-38 Workbook.SaveAs方法参数详解名称FilenameFileFormatPasswordWriteResPasswordReadOnlyRecommendedCreateBackupAccessModeConflictResolutionAddToMruLocalTextVisualLayoutLocal描述一个表示要保存文件的文件名的字符串。可包含完整路径,如果不指定路径,Excel将文件保存到当前文件夹中保存文件时使用的文件格式。对于现有文件,默认采用上一次指定的文件格式;对于新文件,默认采用当前所用 Excel版本的格式它是一个区分大小写的字符串(最长不超过 15 个字符),用于指定文件的保护密码一个表示文件写保护密码的字符串。如果文件保存时带有密码,但打开文件时不输入密码,则该文件以只读方式打开如果为 True,则在打开文件时显示一条消息,提示该文件以只读方式打开如果为 True,则创建备份文件工作簿的访问模式一个 XlSaveConflictResolution 值,它确定该方法在保存工作簿时如何解决冲突。如果设为 xlUserResolution,则显示冲突解决对话框。如果设为 xlLocalSessionChanges,则自动接受本地用户的更改。如果设为 xlOtherSessionChanges,则自动接受来自其他会话的更改。如果省略此参数,则显示冲突处理对话框如果为 True,则将该工作簿添加到最近使用的文件列表中不在美国英语版的Excel中使用不在美国英语版的Excel中使用如果为 True,则以Excel(包括控制面板设置)的语言保存文件。如果为 False(默认值),则以VBA的语言保存文件,其中VBA 通常为美国英语版本,除非从中运行Workbooks.Open的VBA 项目是旧的已国际化的 XL5/95VBA 项目本例文件参见光盘:..\ 第十一章\另存且密码为 123.xlsm11.6.3工作簿拆分〖案例要求〗:将工作簿按工作表拆分,每个工作表存为一个单独的工作簿,保存路径由用户选择。〖知识要点〗:Close、SaveAs〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 拆分工作簿()Dim fd As FileDialog, path As String, sht As Worksheet'弹出对话框,让用户选择文件夹Set fd = Application.FileDialog(msoFileDialogFolderPicker)'如果选择了文件夹则记录地路径IF fd.Show = -1 Thenpath = fd.SelectedItems(1) & IIF(Right(fd.SelectedItems(1), 1) = "\", "", "\")Else: Exit SubEnd IFExcel VBA 程序开发自学通2020-2-26第 370页 /共 508页'遍工作表For Each sht In Sheets'将工作表复制到新工作簿中(相当于新建一个文件,再将当前表复制到其中,但新工作簿中仅仅包括一个工作表)sht.Copy'将新工作簿保存在刚才选择的路径中,且以工作表名做为工作簿名ActiveWorkbook.SaveAs path & sht.Name, xlWorkbookDefault'关闭工作簿ActiveWorkbook.CloseNext shtEnd Sub以上过程首先让用户选择一个存放工作簿的路径,如果未选择则退出程序;如果选择了路径则逐个复制工作表到新工作簿中,将工作簿保存在该路径下,以工作表名做为工作簿名称。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“拆分工作簿”,程序会弹出一个“浏览”对话框,假设选择 C 盘,那么程序会将当前工作簿中每个工作表保存为一个工作簿,以工作表名命名。图 11.119 和图 11.120 分别为拆分前的工作簿和拆分后的所有文件列表:图 11.119 拆分前的工作簿图 11.120 拆分后的工作簿〖语法补充〗:(1)Workbook.Close 方法表示关闭工作簿。它的语法如下:表达式.Close(SaveChanges, Filename, RouteWorkbook)其中三参数的含义如下:表 11-39 Workbook.Close方法参数详解名称SaveChangesFilenameRouteWorkbook描述如果工作簿中没有改动,则忽略此参数。如果工作簿中有改动但工作簿显示在其他打开的窗口中,则忽略此参数。如果工作簿中有改动且工作簿未显示在任何其他打开的窗口中,则由此参数指定是否应保存更改。如果设为 True,则保存对工作簿所做的更改。如果工作簿尚未命名,则使用FileName。如果省略 Filename,则要求用户提供文件名以此文件名保存所做的更改如果工作簿不需要传送给下一个收件人(没有传送名单或已经传送),则忽略此参数。否则,Microsoft Excel 根据此参数的值传送工作簿。如果设为 True,则将工作簿传送给下一个收件人。如果设为 False,则不发送工作簿。如果忽略,则要求用户确认是否发送工作簿。(2)关闭一个未保存的工作簿时会提示用户是否保存,而 Workbook.Close 的第Excel VBA 程序开发自学通2020-2-26第 371页 /共 508页一参数使用 False 时可以禁止该提示。本例文件参见光盘:..\ 第十一章\拆分工作簿.xlsm11.6.4批量打开文件〖案例要求〗:批量打开选定的文件。〖知识要点〗:Open〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 批量打开文件()Dim fd As FileDialog, Item As Integer'弹出一个浏览文件的窗口,可以多选目标文件Set fd = Application.FileDialog(msoFileDialogFilePicker)'如果选择了文件IF fd.Show = -1 Then'遍历所有文件For Item = 1 To fd.SelectedItems.Count'逐个打开文件Workbooks.Open (fd.SelectedItems(Item))Next ItemEnd IFEnd Sub以上过程会弹出一个“浏览”对话框,当用户选择文件后程序对选中的文件逐一打开。如果某文件有打开密码,将会中断,等待用户输入密码后继续执行。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量打开文件”,将弹出对个对“浏览”话框,通过鼠标拖动或者 Ctrl、Shift 键配合左右键单击都可以多选文件,见图 11.121 所示:Excel VBA 程序开发自学通2020-2-26第 372页 /共 508页图 11.121 选择待打开的文件(4)单击对话框中“确定”按钮后,程序会打开所有选定的文件。〖语法补充〗:(1)Workbook.Open 方法用二打开一个工作簿,它的语法如下:Workbook.Open(FileName,UpdateLinks,ReadOnly,Format,Password,WriteResPassword, IgnoreReadOnlyRecommended, Origin, Delimiter, Editable, Notify,Converter, AddToMru, Local, CorruptLoad)Workbook.Open 方法的所有参数都是可选参数。各参数含义如下:表 11-40 Workbook.Open方法参数详解名称CorruptLoadUpdateLinksReadOnlyFormatPasswordWriteResPasswordIgnoreReadOnlyRecommendedOriginDelimiterEditableNotifyConverterAddToMruLocalCorruptLoad描述String 类型。要打开的工作簿的文件名指定更新文件中链接的方式。如果省略此参数,则提示用户指定链接的更新方式如果为 True,则以只读模式打开工作簿。如果Excel正在打开文本文件,则由此参数指定分隔符。如果省略此参数,则使用当前的分隔符一个字符串,包含打开受保护工作簿所需的密码。如果省略此参数并且工作簿已设置密码,则提示用户输入密码一个字符串,包含写入受保护工作簿所需的密码。如果省略此参数并且工作簿已设置密码,则提示用户输入密码如果为 True,则不让Excel 显示只读的建议消息如果该文件为文本文件,则此参数用于指示该文件来源于何种操作系统。可为以下 XlPlatform常量之一:xlMacintosh、xlWindows 或xlMSDOS如果该文件为文本文件并且 Format 参数为 6,则此参数是一个字符串,指定用作分隔符的字符如果文件为 Excel 4.0 加载宏,则此参数为 True 时可打开该加载宏以使其在窗口中可见。如果此参数为 False 或被省略,则以隐藏方式打开加载宏,并且无法设为可见。本选项不能应用于由 Excel 5.0 或更高版本的 Excel 创建的加载宏当文件不能以可读写模式打开时,如果此参数为 True,则可将该文件添加到文件通知列表。Excel 将以只读模式打开该文件并轮询文件通知列表,并在文件可用时向用户发出通知。如果此参数为 False 或被省略,则不请求任何通知,并且不能打开任何不可用的文件打开文件时试用的第一个文件转换器的索引。首先试用的是指定的文件转换器;如果该转换器不能识别此文件,则试用所有其他转换器。转换器索引由 FileConverters 属性返回的转换器行号组成如果为 True,则将该工作簿添加到最近使用的文件列表中。默认值为False。如果为 True,则以Excel的语言保存文件。如果为 False(默认值),则以VBA的语言保存文件,其中VBA 通常为美国英语版本,除非从中运行 Workbooks.Open 的 VBA 项目是旧的已国际化的 XL5/95 VBA 项目可为以下常量之一:xlNormalLoad、xlRepairFile 和 xlExtractData。如果未指定任何值,则默认行为通常为普通加载,但如果 Excel 已尝试打开该文件,则可以是安全加载或数据恢复状态。首先尝试普通加载。如果 Excel 在打开文件时停止操作,则尝试安全加载状态。如果 ExcelExcel VBA 程序开发自学通2020-2-26第 373页 /共 508页再次停止操作,则尝试数据恢复状态本例文件参见光盘:..\ 第十一章\批量打开工作簿.xlsm11.6.5导入文本文件到当前工作簿〖案例要求〗:导入文本文件到当前工作簿第三个工作表中。〖知识要点〗:Sheets〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 导入文件且以逗号分列()'将文件导入后存入第三个工作表中With ThisWorkbook.Sheets(3).QueryTables.Add(Connection:="TEXT;C:\成绩表.txt",Destination:=ThisWorkbook.Sheets(3).Range("$A$1"))'以逗号进行分列.TextFileCommaDelimiter = True'更新外部数据.Refresh BackgroundQuery:=FalseEnd WithEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“导入文件且以逗号分列”,那么 C 盘中名为“成绩表.txt”的文本文件中的数据将瞬间导入到当前工作表簿第三个工作表中。图 11.122 和图 11.123 是文本文件内容导入到Excel 后的显示状态。图 11.122 文本文件11.123 导入文本文件到工作簿〖语法补充〗:(1)Workbook.Sheets 属性返回一个 Sheets 集合,它代表指定工作簿中所有工作表。利用参数 Item 可以访问其它某个工作表。例如 Worksheets("Book1").Sheets (2)表示 Book2 工作簿中第二个工作表ActiveWorkbook.Sheets ("2")表示当前活动工作表中名为“2”的工作表,而非第 2个工作表ThisWorkbook .Sheets(Date & "")表示 VBA 代码所在工作簿中名为今日日期的工作表ThisWorkbook .Sheets(Date)表示 VBA 代码所在工作簿中序号为今日日期的工作Excel VBA 程序开发自学通2020-2-26第 374页 /共 508页表,日期转序号可以使用“Date * 1”。假设今天是 2009 年 5 月 27 日,那么该代码表示引用工作簿中第 39960 个工作表。这是 Sheets 的参数使用文本和日期的差异。本例文件参见光盘:..\ 第十一章\导入文本文件.xlsm11.6.6保存并关闭本工作簿以外的工作簿〖案例要求〗:保存并关闭本工作簿以外的工作簿。〖知识要点〗:Name〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 保存并关闭本工作簿以外的工作簿()Dim Wkb As Workbook'遍历工作簿For Each Wkb In Workbooks'如果不是本工作簿IF Wkb.Name ThisWorkbook.Name ThenWkb.Save '保存文件Wkb.Close'关闭End IFNext WkbEnd Sub以上过程在打开了多个工作簿时可以保存并关闭代码所在工作簿以外的所有工作簿。如果工作簿已曾经保存过,则仅仅保留更新的信息,如果工作簿未保存过,则将工作簿保存在当前文件夹。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“保存并关闭本工作簿以外的工作簿”,那么其它工作簿会瞬间保存更新的数据并关闭。如果仅仅关闭不保存,那么条件语句中间的两句代码可以修改为一句:Wkb.Close (False)〖语法补充〗:(1)Workbook.Name 属性返回一个 String 值,它代表对象的名称,可读不可写。如果工作簿未保存,那么它的名称不带后缀名,例如“Book1”、“Book20”等等。当保存后会根据格式不同显示出后缀名,例如“生产表.xls”、“Book2.xlsm”等等。(2)修改打开工作簿的名称唯一方法是 Saveas 方法另存工作簿。本例文件参见光盘:..\ 第十一章\保存并关闭所有工作簿.xlsm11.6.7每 30 分钟备份工作簿〖案例要求〗:将当前工作簿每 30 分钟备份一次。备份路径可选,每次备份的文件以“备份 1”、“备份 2”等等加原名做为文件名。〖知识要点〗:Path、Name、Saveas〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Excel VBA 程序开发自学通2020-2-26第 375页 /共 508页Dim fd As FileDialog, path As String, File_name As String, File_Path As StringSub 每 30 分钟备分工作簿()'弹出对话框,让用户选择文件夹Set fd = Application.FileDialog(msoFileDialogFolderPicker)'如果选择了文件夹则记录路径IF fd.Show = -1 Thenpath = fd.SelectedItems(1) & IIF(Right(fd.SelectedItems(1), 1) = "\", "", "\")ElseExit SubEnd IF'获取文件名与文件路径File_name = ActiveWorkbook.NameFile_Path = ActiveWorkbook.path & "\"Call 备份End Sub以上过程让用户选择一个存放备份文件的路径,然后提取当前文件的文件名与路径(必须确保工作簿已经保存过)。Sub 备份()'声明一个静态变量Static Item'设置计划任何 每 30 分钟执行一次Application.OnTime EarliestTime:=Now + TimeValue("00:00:05"), Procedure:="备份"Item = Item + 1'关掉提示,防止弹出覆盖文件的对话框Application.DisplayAlerts = False'备份到指定的文件夹中,且在原文件名之前添加前缀“备份 1”\“备份 2”等等ActiveWorkbook.SaveAs Filename:=path & " 备 份 " & Item & File_name,CreateBackup:=False'再返回原来路径,覆盖原文件ActiveWorkbook.SaveAs Filename:=File_Path & File_name, CreateBackup:= FalseApplication.DisplayAlerts = TrueEnd Sub以上过程每 30 分钟执行一次,将当前工作簿另存到指定的备份文件夹中,再返回来有路径,覆盖原有文件。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“每30 分钟备分工作簿”,程序会弹出一个对话框,让用户选择备份文件路径。假设选择C 盘根目录,那么程序会每 30 分钟备份一次工作工作簿,当 120 分钟后在 C 盘将生成 5 个备份的文件,见图 11.124 所示:图 11.124 C 盘中的备份文件〖语法补充〗:(1)Workbook.path 属性它代表工作簿的完整路径,不包括末尾的分隔符和应用程序名称。例如工作簿“ABC.xlsx”保存在 C 盘中 BCD 文件夹中,那么它的 Path 属Excel VBA 程序开发自学通2020-2-26第 376页 /共 508页性为“C:\BCD”。(2)VBA 的 Saveas 方法有一个 CreateBackup 参数可以控制文件是否在保存时备份,然后笔者经过多次测试,在 Excel 2007 使用时无法确保能正常的备份,远远不如本例的代码容易控制,且可以随心所欲选择备份路径和文件名。本例文件参见光盘:..\ 第十一章\每 30 分钟备份.xlsm11.6.8将当前工作簿备份到 D 盘〖案例要求〗:将当前工作簿备份到 D 盘。〖知识要点〗:SaveCopyAs〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 备份到 D 盘()'如果未保存则显示“另存为”对话框IF ActiveWorkbook.Path = "" Then Application.Dialogs(xlDialogSaveAs).Show'备份文件ActiveWorkbook.SaveCopyAs "D:\备份文件" & ActiveWorkbook.NameEnd Sub以上过程首先检查工作簿是否保存,如果未保存为显示另存对话框,让用户保存文件。然后将当前工作簿备份到 D 盘根目录中,且在原文件中加“备份文件”,不管执行多少次备份,仅仅产生一个备份。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“备份到 D 盘”,当前工作簿将备份到 D 盘根目录,且添加前置字符串“备份文件”。〖语法补充〗:(1)Workbook.SaveCopyAs 方法用于将指定工作簿的副本保存到文件,但不修改内存中的打开工作簿。其语法如下:Workbook.SaveCopyAs (Filename)其中参数 Filename 表示备份文件的文件名,包含路径。(2)SaveCopyAs 用于备份文件,虽然它也另存文件,但不改变活动工作簿;而Saveas 方法在另存工作簿后,会退出原有工作簿,激活另存后的新工作簿,这是两者的区别。本例文件参见光盘:..\ 第十一章\当前工作簿备份到 D 盘.xlsm11.6.9清除所有打开工作簿的密码〖案例要求〗:将当前打开的工作簿的密码清空。〖知识要点〗:HasPassword〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 清除密码()Dim Wkb As Workbook'关掉警告提示,防止询问是否覆盖文件Excel VBA 程序开发自学通2020-2-26第 377页 /共 508页Application.DisplayAlerts = False'遍历所有工作簿For Each Wkb In Workbooks'如果在密码则另存,密码为空IF Wkb.HasPassword Then Wkb.SaveAs , , ""Next Wkb'恢复提示Application.DisplayAlerts = TrueEnd Sub以上过程遍历所有打开的工作簿,逐一判断是否存在密码保护,如果有密码则以同名文件另存,以空文本为密码。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“清除密码”,那么打开的所有工作簿会瞬间清除密码。〖语法补充〗:(1)Workbook.HasPassword 属性如果指定工作簿有密码保护,则该属性值为True。本例文件参见光盘:..\ 第十一章\清除打开工作簿的密码.xlsm11.6.10获取工作簿建立时间和最后一次保存时间〖案例要求〗:获取工作簿建立时间和最后一次保存时间。〖知识要点〗:BuiltinDocumentProperties〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 报告创建与修改时间()On Error Resume Next'则提示创建时间与修改时间MsgBox "创建时间:" & ActiveWorkbook.BuiltinDocumentProperties(11) & Chr(10) _& "最后修改时间:" & ActiveWorkbook.BuiltinDocumentProperties(12), 64, "友情提示"'如果有错误提表示工簿且未保存IF Err 0 Then MsgBox "本工作簿未保存"End Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“报告创建与修改时间”,如果工作簿未保存则产生图 11.125 所示的提示,如果已保存过则提示其创建时间和最后的修改时间,见图 11.126 所示:11.125 未保存工作簿之提示图 11.126 提示创建时间与最后修改时间〖语法补充〗:(1)Workbook.BuiltinDocumentProperties 属性返回一个 DocumentProperties 集Excel VBA 程序开发自学通2020-2-26第 378页 /共 508页合,该集合表示指定工作簿的所有内置文档属性。其内置属性主要包括文件的标题、创建者、创建时间、修改时间、页数、字数等等。可以修改以下代码罗列出所有内置属性:Sub 文件内置属性()Dim Item As Byte, pWorksheets(1).ActivateFor Each p In ActiveWorkbook.BuiltinDocumentPropertiesItem = Item + 1Cells(Item, 1).Value = p.NameNextEnd Sub(2)文档的属性区分内置属性和自定义属性。用户可以自定义一个或者多个属性。下例过程自定义三个文件属性,包含三种数据类型:Sub 自定义文件属性()With ActiveWorkbook.CustomDocumentProperties.Add Name:="文件编号", LinkToContent:=False, _Type:=msoPropertyTypeNumber, Value:=1234.Add Name:="分类", LinkToContent:=False, _Type:=msoPropertyTypeString, Value:="机密".Add Name:="更新日期", LinkToContent:=False, _Type:=msoPropertyTypeDate, Value:=DateEnd WithEnd Sub以上过程定义了三个文件属性,包括编号、分类与更新日期。如果该 Name 已经存在则无法定义,必须改名。所以以上过程执行第二次时一定会产生错误而中断。Sub 读取属性()With ActiveWorkbook.CustomDocumentPropertiesMsgBox "文件编号:" & .Item("文件编号") & Chr(10) _& "文件分类:" & .Item("分类") & Chr(10) _& "更新日期:" & .Item("更新日期"), 64, "文档属性"End WithEnd Sub如果读取的属性未定义,也会运行时错误而中断程序。执行以上过程后结果如下:图 11.127 获取自定义的文件属性本例文件参见光盘:..\ 第十一章\创建与保存时间.xlsm11.6.11记录文件打开次数〖案例要求〗:记录文件打开次数。〖知识要点〗:CustomDocumentPropertiesExcel VBA 程序开发自学通2020-2-26第 379页 /共 508页〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub Auto_open()'防错On Error Resume NextDim Open_Count As LongWith ActiveWorkbook.CustomDocumentProperties'获取上次打开的次数Open_Count = .Item("打开次数").Value'如果有错误If Err 0 Then'添加新的属性,其值为 1.AddName:="打开次数",Type:=msoPropertyTypeNumber, Value:=1Else'否则文件属性累加 1.Item("打开次数") = .Item("打开次数").Value + 1End If'报各次数MsgBox "本文件打开次数:" & .Item("打开次数").ValueEnd WithEnd SubLinkToContent:=False,以上过程利用 Auto_open 做为文件名,表示工作簿每次开启时都执行过程。在执行过程时,会读取自定义属性的值,如果不存在则新加值为的“打开次数”属性,否则在原值基础上累加 1。(3)保存并关闭工作簿,当重新开启工作簿后,会弹出文件开启次数的对话框,多次开次后会累加该值,见图 11.128 所示。图 11.128报告文件开启次数〖语法补充〗:( 1 ) Workbook.CustomDocumentProperties 属 性 返 回 或 设 置 一 个DocumentProperties 集合,该集合表示指定工作簿的所有自定义文档属性。(2)通过以下过程可以获取所有自定义属性的名称及值Sub 获取自定义属性()Dim Item As ByteFor Each p In ActiveWorkbook.CustomDocumentPropertiesItem = Item + 1Cells(Item, 1).Value = p.NameCells(Item, 2).Value = p.ValueNextEnd Sub本例文件参见光盘:..\ 第十一章\记录文件打开次数.xlsmExcel VBA 程序开发自学通11.6.122020-2-26第 380页 /共 508页切换图形对象隐藏与显示〖案例要求〗:切换图形对象的显示状态,如果处于隐藏状态则显示,如果处于显示状态则隐藏,可以反复切换。〖知识要点〗:DisplayDrawingObjects〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 控制工作簿中图片的显示与否()'如果当前状态为显示则隐藏图形对象,否则显示图形对象ActiveWorkbook.DisplayDrawingObjectsIIF(ActiveWorkbook.DisplayDrawingObjects = xlHide, xlPlaceholders, xlHide)End Sub=(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“DisplayDrawingObjects”,如果当前工作簿的当前图形对象处于隐藏状态,那么会显示出所有插入的图形对象,包括图片、形状、嵌入式图表、艺术字、ActiveX 控件、文本框等等;(4)再次执行宏,当前工作簿的所有工作表中的图形对象都会隐藏起来,而且插入图形、艺术字等等按钮都显示灰色状态,无法使用,见图 11.129 所示:图 11.129隐藏图形状态下【插入】功能区部分按钮呈灰色〖语法补充〗:(1)Workbook.DisplayDrawingObjects 属性返回或设置形状的显示方式。它包括三种显示方式,可以利用内置的常量来控制,表 11-41 即 DisplayDrawingObjects 属性的常量列表表 11-41DisplayDrawingObjects属性的常量列表常量xlDisplayShapesxlPlaceholdersxlHide说明显示所有形状仅显示占位符显示所有形状(2)DisplayDrawingObjects 属性对当前工作簿所有工作表生效,但重启 Excel后会恢复为默认值。所以如果希望永远按自己设置的方式控制图形,那么可以设计一个加载宏,配合应用程序级别的事件使代码都所有工作簿且任何时候都生效。本例文件参见光盘:..\ 第十一章\切换图形对象显示状态.xlsmExcel VBA 程序开发自学通11.6.132020-2-26第 381页 /共 508页设计一个查看一次即自动删除的工作簿〖案例要求〗:设计一个查看一次即自动删除的工作簿,关闭文件时会自我销毁,且不经过回收站。〖知识要点〗:Saved、ChangeFileAccess、FullName〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub Auto_Close() '关闭工作簿时自动执行With ThisWorkbook'标识为已保存状态.Saved = True'设为只读模式.ChangeFileAccess Mode:=xlReadOnly'删除工作簿Kill .FullName'关闭且不保存.Close FalseEnd WithEnd Sub以上过程会在工作簿关闭时自我删除,请测试代码前备份文件。(3)保存工作簿,然后再另存一次。因为工作簿关闭时会自我删除,所以必须存一次再另存一次,那么第二次另存的文件会自我删除,第一次保存的文件却存在,那么该文件即为只能查看一次就会自我删除的文件。(4)将工作簿复制到其它电脑中,如果该电脑中的 Excel 可以执行宏,那么在查看工作簿一次,关闭时工作簿会自动消失,回收站也找不回来。〖语法补充〗:(1)Workbook.Saved 属性用于控件工作簿的保存状态,可读也可写。如果仅仅需要将工作簿标识为已保存状态,却并非事实上对它进行保存,那么可以直接赋值为True,那么关闭工作簿时将不会循询问用户是否保存工作簿的修改。(2)Workbook.ChangeFileAccess 方法用于更改工作簿的访问权限。它的语法如下:Workbook.ChangeFileAccess(Mode, WritePassword, Notify)其第一参数 Mode 表示工作簿的访问模式,如果赋值为 xlReadOnly 则表示只读状态;如果赋值为 xlReadWrite 则表示可读写状态。(3)Workbook.FullName 属性表示工作表的全称,包括其磁盘路径与工作簿名。相当于 Path 与 Name 之综合体。本例文件参见光盘:..\ 第十一章\只能查看一次即自我删除.xlsm11.6.14禁止插入新工作表〖案例要求〗:禁止当前工作簿中插入新工作表,即保护工作簿的结构。〖知识要点〗:ProtectStructure、Protect〖实现步骤〗:Excel VBA 程序开发自学通2020-2-26第 382页 /共 508页(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 禁止插入新表()'如果已经保护工作簿中的工作表次序,那么退出IF ActiveWorkbook.ProtectStructure ThenMsgBox "已经处于保护状态", 64Exit SubEnd IF'保扩工作表次序,禁止插入及删除,密码为 123ActiveWorkbook.Protect Structure:=True, Password:="123"End Sub以上过程对工作簿的结构进行保护,从而禁止插入工作表。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“禁止插入新表”,程序会保护当前工作簿的工作表结构,禁止添加、删除工作表。当在工作表标签处单击右键时,【插入】、【删除】、【命名】、【工作表标签颜色】等等与工作表相关的操作菜单都呈灰色禁用状态,同时功能区【审阅】\【保所工作簿】\【保护结构和窗口】也呈勾选状态。如果需要取消限制,可以单击菜单【保护结构与窗口】,并输入密码 123。图 11.130 插入、删除工作表的菜单呈灰色图 11.131 保护结构与窗口菜单呈勾选状态〖语法补充〗:(1)Workbook.ProtectStructure 属性表示工作簿中的工作表次序是否被保护,如果工作簿中的工作表次序处于保护状态,则该属性值为 True。(2)Workbook.Protect 方法保护工作簿使其不被修改。具体语法如下:Workbook.Protect(Password, Structure, Windows)其中三个可选参数的含义见表 11-42:表 11-42 Workbook.Protect方法参数详解名称PasswordStructureWindows描述一个字符串,该字符串为工作表或工作簿指定区分大小写的密码。如果省略此参数,不用密码就可以取消对工作表或工作簿的保护如果为 True,则保护工作簿结构(工作表的相对位置)。默认值是 False如果为 True,则保护工作簿窗口。如果省略此参数,则窗口不受保护本例文件参见光盘:..\ 第十一章\禁止插入工作表.xlsmExcel VBA 程序开发自学通11.6.152020-2-26第 383页 /共 508页不打开工作簿而提取数据〖案例要求〗:从关闭的工作簿中获取数据,工作簿的路径、工作表名名、区域等等可选。假设当前工作簿同路径下有一个文件夹名为“人事资料”,其中有一个工作簿“人事报表.xls”,现要求不打开工作簿的前提下获取其中任何工作表任何区域的值。〖知识要点〗:Path〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:'声明一个带有四个参数的 Sub 过程Sub 取值(路径 As String, 文件 As String, 工作表, 单元格 As String)'将目标区域复制到以当前表活动单元格为左上角的相同大小的区域中With ActiveCell.Resize(Range(单元格), Rows.Count, Range(单元格), Columns.Count)'在指定区域输入公式,该公式引用指定路径下的工作表数据,可以是单元格也可以是区域.FormulaArray = "='" & 路径 & "\[" & 文件 & "]" & 工作表 & "'!" & 单元格'将公式转换成值.Value = .ValueEnd WithEnd Sub以上过程带有四个参数,分别为路径、工作簿名、工作表名和区域地址,表示从该处获取数据。程序通过公式将目标工作簿中指定区域的值引入当前表中,数据存放区域为活动单元格为基准向右、向下扩展至与目标区域相同大小的区域。Sub 不打开工作簿而获取其值 1()'调用 Sub 过程,指定参数取值 ThisWorkbook.Path & "\人事资料", "人事报表.xls", "部门人员统计", "A1:B9"End Sub获取指定工作表的值,路径为代码所在工作簿相同路径下一层名为“人事资料”的文件夹,工作簿名称为“人事报表.xls”,工作表名为“部门人员统计”,区域为“A1:B9”。用户也可以根据需要,调用任意文件夹中的工作表数据,但是必须注意不能缺少路径中的“\”Sub 不打开工作簿而获取其值 2()'调用 Sub 过程,指定参数取值 ThisWorkbook.Path & "\人事资料", "人事报表.xls", "人事资料", "A1:C42"End Sub再取获取同工作簿中另一个工作表的值.(3)返回工作表,激活单元格 XFD2,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“不打开工作簿而获取其值 1”,程序立即弹出图 11.132 所示对话框。因为 XFD 是工作表中最一列,而目标区域有两列,所以无法存放数据;(4)激活单元格 A2,再执行程序“不打开工作簿而获取其值 1”,程序会将“人事资料[人事报表.xls]部门人员统计'!A1:B9”中的值导入到活动单元格区域,见图11.133 所示:Excel VBA 程序开发自学通2020-2-26图 11.132 区域无法存放目标数据时产生提示第 384页 /共 508页图 11.133 不打开工作簿而引用数据(5)再次执行过程“不打开工作簿而获取其值 2”,那么“人事资料[人事报表.xls]人事资料'!A1:C42”中的值导入到活动单元格区域中。〖语法补充〗:(1)本例输入过程“取值”的所有参数都是文本字符串,不能使用其它数据类型。例如第四参数使用“[a1:b2]”会导致程序错误。同时工作簿的路径与工作簿之间之间必须有一个“\”,否则也会产生错误。(2)本例使用等号引用目标工作簿的值,那么目标区域中若有空白单元格,当前工作表会以零值显示。本例文件参见光盘:..\ 第十一章\不打开工作簿而获取其数据.xlsm11.6.16合并指定文件夹下每个工作簿中三月生产表到一个工作簿〖案例要求〗:将指定文件夹中所有工作簿中名为“三月”的工作表合并到一个工作簿中。即文件夹中有多个以“车间”命名的报表,每个车间的报表工作簿中分别有“一月”、“二月”、“三月”等等工作表,现需利用 VBA 实现所有工作簿中的“三月”工作表合并到一个新工作簿中,合并后该工作簿中的工作表数量等于原有工作簿数量,且每个工作表正好对应于现有车间报表中的“三月”报表。如果延伸一下,可以实现原来按车间命名的报表修改为按月份进行分类,即每个车间的报表包含多个月份的产量,程序可以将它修改为每份报表按月份命名,每个月报表中包括所有车间的产量。Excel VBA 程序开发自学通2020-2-26图 11.134 待合并的工作簿第 385页 /共 508页图 11.135待合并工作簿中的工作表〖知识要点〗:SaveAs、Add、Close〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:'声明一个带有四个参数的 Sub 过程Sub 取值(路径 As String, 文件 As String, 工作表, 单元格 As String)'将目标区域复制到当前月相同大小的区域中With Range(单元格)'在指定区域输入公式,该公式引用指定路径下的工作表数据,可以是单元格也可以是区域.FormulaArray = "='" & 路径 & "\[" & 文件 & "]" & 工作表 & "'!" & 单元格'将公式转换成值.Value = .ValueEnd WithEnd Sub以上过程用于将指定目录下的工作簿中指定工作表数据复制到当前表,仅仅复制数据,不包括格式。Sub 合并三月报表到一个工作簿()'关闭屏幕刷新,加快速度Application.ScreenUpdating = FalseDim FolderName As String, 工 作 簿 名 As String, Arr() As String, 工 作 簿 数 量 AsInteger, Item As Integer'创建文件夹中工作簿列表,将所有工作表簿名导入数组 Arr 中工作簿名 = Dir("D:\生产表\*.xls")While 工作簿名 ""工作簿数量 = 工作簿数量 + 1ReDim Preserve Arr(1 To 工作簿数量)Arr(工作簿数量) = 工作簿名工作簿名 = DirWend'如果没有工作簿则退出程序IF 工作簿数量 = 0 Then Exit SubWorkbooks.Add '添加一个新工作簿,用于存放所有车间的三月产量'如果新工作簿中工作表数量小于待合并的工作簿数量则新建工作表,使其个等于工作簿Excel VBA 程序开发自学通2020-2-26第 386页 /共 508页数量IF Application.SheetsInNewWorkbook < 工作簿数量 ThenSheets.Add , , 工作簿数量 - Application.SheetsInNewWorkbookEnd IF'遍历工作表For Item = 1 To Sheets.Count'将工作表名修改为对应的工作簿名(车间名)Sheets(Item).Name = Replace(Arr(Item), ".xls", "")Sheets(Item).Select'引用该工作簿中“三月”工作表 A1:B10 的数据取值 "D:\生产表", Arr(Item), "三月", "A1:B10"Next ItemApplication.ScreenUpdating = True'最后合并完成的工作簿包括所有车间名为三月的数据总和,'合并后的新工作簿中每个表以原有的工作簿名命名End Sub以上过程首先统计“D:\生产表”文件夹中有多少个工作簿,然后新建一个工作簿,将根据文件夹中工作簿的数量来决定新工作簿中的工作表数量,且所有工作表以待合并工作簿的名称命名。最后利用循环将每个工作簿中的数据复制到新工作簿对应的工作表中。执行本过程请确保 D 盘中有相应的文件夹和工作簿。可以将光盘中的文件复制到 D 盘或者直接修改代码中的路径两种方式确保代码正确执行。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并三月报表到一个工作簿”,那么“D:\生产表”文件夹下所有工作簿中名为三月的报表合并到新工作簿。合并所有工作簿中包括所有车间的三月产量表,见图 11.136 所示。图 11.136 合并后的三月产量表(4)如果需要将四个车间报表中一月、二月、三月的产量全部重新合并为三个报表,分别为所有车间一月产量、二月产量、三月产量,那么可以修改代码,再加入一次循环来完成。即按车间分类的产量表修改为按月份分类:Sub 合并每个月报表为独立工作簿()Application.ScreenUpdating = FalseDim FolderName As String, 工 作 簿 名 As String, Arr() As String, 工 作 簿 数 量 AsInteger, Item As Integer, Months As Byte工作簿名 = Dir("D:\生产表\*.xls")While 工作簿名 ""工作簿数量 = 工作簿数量 + 1ReDim Preserve Arr(1 To 工作簿数量)Excel VBA 程序开发自学通2020-2-26第 387页 /共 508页Arr(工作簿数量) = 工作簿名工作簿名 = DirWendOn Error Resume Next'在 D 盘“生产表”文件夹中建立一个“按月份分类”的文件夹,用于存放重新分类的工作簿MkDir "D:\生产表\按月份分类"IF 工作簿数量 = 0 Then Exit Sub'循环三次,别取出三个月的车间产量表For Months = 1 To 3Workbooks.AddIF Application.SheetsInNewWorkbook < 工作簿数量 ThenSheets.Add , , 工作簿数量 - Application.SheetsInNewWorkbookEnd IFFor Item = 1 To Sheets.CountSheets(Item).Name = Replace(Arr(Item), ".xls", "")Sheets(Item).Select取 值 "D:\ 生 产 表 ", Arr(Item), WorksheetFunction.Text(Months, "[DBNum1]0月"), "A1:B10"Next Item'将新工作簿存入新建文件夹中ActiveWorkbook.SaveAs "D:\ 生 产 表 \ 按 月 份 分 类 \" &WorksheetFunction.Text(Months, "[DBNum1]0 月")'关闭工作簿ActiveWorkbook.CloseNext MonthsApplication.ScreenUpdating = True'最后的结果为将按车间分类,每个工作簿包含三个月产量的工作簿重新分类为按月份分类,每个工用簿包含四个车间的产量End Sub执行以上过程可以实现“D:\生产表”中创建一个“按月份分类”的文件夹,然后将原有四个车间的报表重新合并为按月份分类的报表。图 11.137 是一月份四个车间的产量表,图 11.138 是程序生成的新文件夹及三个新产量表。图 11.137 所有车间一月产量表图 11.138 将原报表按月份分类〖语法补充〗:(1)本例是复制所有车间产量表中的数据,那么可以通过等号来取值。而且在预先知道每个工作表中已用区域的前提下进行,否则不能获取区域地址。如果需在让程序自自己决定复制哪个区域,而且将工作表中的格式设置也复制到新工作簿,那么Excel VBA 程序开发自学通2020-2-26第 388页 /共 508页只能打开工作簿才后做到。本书的最后一章设计 Excel 百宝箱插件将会详述设计思路,及提供详细代码。本例文件参见光盘:..\ 第十一章\合并指定文件夹下每个工作簿中三月生产表到一个工作簿.xlsm11.6.17建立指定文件夹下所工作簿目录和工作表目录〖案例要求〗:对指定主目录下所有工作簿中所有工作表建立目录,工作簿目录在 A 列,工作表目录在 B 列。〖知识要点〗:Name、Open、Close〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 建立所有工作簿中的工作表目录()Application.ScreenUpdating = FalseDim fd As FileDialog, path As String, Old_Name As String'弹出对话框,让用户选择文件夹Set fd = Application.FileDialog(msoFileDialogFolderPicker)'如果选择了文件夹则记录路径IF fd.Show = -1 Thenpath = fd.SelectedItems(1) & IIF(Right(fd.SelectedItems(1), 1) = "\", "", "\")ElseExit SubEnd IF'记录当前工作簿名Old_Name = ActiveWorkbook.NameDim 工作簿名 As String, Arr() As String, 工作簿数量 As Integer, Item As Integer, j AsInteger, k As Integer'将工作表簿名存入数组工作簿名 = Dir(path & "*.xls*")While 工作簿名 ""工作簿数量 = 工作簿数量 + 1ReDim Preserve Arr(1 To 工作簿数量)Arr(工作簿数量) = 工作簿名工作簿名 = DirWendIF 工作簿数量 = 0 Then Exit Subj=1For k = 1 To 工作簿数量j=j+1'记录工作簿名称到 A 列Workbooks(Old_Name).Sheets(1).Cells(j, 1) = Arr(k)'逐个打开工作簿Workbooks.Open path & Arr(工作簿数量)'在所有工作表循环For Item = 1 To Sheets.Countj=j+1'记录所有工作表名称到 B 列Workbooks(Old_Name).Sheets(1).Cells(j, 2) = Sheets(Item).NameExcel VBA 程序开发自学通2020-2-26第 389页 /共 508页Next Item'关闭工作簿ActiveWorkbook.Close (False)NextApplication.ScreenUpdating = TrueEnd Sub以上过程首先弹出一个文件夹“浏览”对话框,让用户选择一个文件夹,程序将取出其中所有 Exeel 文件的工作表目录。注意“浏览”对话框是用于获取文件夹目录,无法看到文件的。然后利用 DIR 函数统计其中文件个数,如果数量为 0 则退出程序,否则记录所有文件名。最后逐个打开工作簿,再逐一循环所有工作表,将工作簿名存入 A 列,工作表名存入 B 列。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“建立所有工作簿中的工作表目录”,程序将弹出图 11.139 所示对框。当选择文件夹并单击“确定”按钮后,根据文件夹中工作簿的多少会花费不同的时间建立目录。图 11.140是“生产表”目录下四个工作簿的所有工作表目录:图 11.139 浏览文件夹图 11.140 工作簿与工作表目录〖语法补充〗:(1)用户浏览目录夹时,如果选择了文件夹,那么路径最后右边没有“\”;如果选择磁盘根目录,那么在路径右边有“\”。所以在程序中需要利用 IF 配合 Right 来判断路径中是否有“\“,这点很重要。(2)Dir 函数可以表示一个文件名、目录名或文件夹名称,它必须与指定的模式或文件属性、或磁盘卷标相匹配。通常用它来判断文件、目录是否存在或者文件路径是否规范等等。在本书第 18 章将详述其语法与功能。本例文件参见光盘:..\ 第十一章\建立所有工作簿中的工作表目录.xlsm11.6.18 断开与其它工作簿的数据链接〖案例要求〗:断开与其它工作簿的数据链接,即部分单元格引用了其它工作簿的数据,为了防止被引用的工作簿删除后当前表数据产生错误,利用 VBA 将当前工Excel VBA 程序开发自学通2020-2-26第 390页 /共 508页作簿所有工作表的外部引用转换成值。〖知识要点〗:BreakLink〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 断开与其它工作簿的数据链接()'将所有外部链接转换成值ActiveWorkbook.BreakLinkName:=ActiveWorkbook.LinkSources(Type:=xlLinkTypeExcelLinks)(1),Type:=xlLinkTypeExcelLinksEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“断开与其它工作簿的数据链接”,那么当前工作簿所有工作表的外部引用都会转换成值。〖语法补充〗:(1)Workbook.BreakLink 方法可将将链接到其它工作簿或者 OLE 源的公式转换为值。语法如下:Workbook.BreakLink(Name, Type)第一参数表示链接的名称,第二参数表示链接类型,当类型为xlLinkTypeExcelLinks 时表示 Excel 工作表的链接。本例文件参见光盘:..\ 第十一章\断开与其它工作簿的数据链接.xlsm11.7 Windows 对象案例Windows 对象是 Excel 中所有 Window(窗口)对象的集合。本节对窗口的应用进行演示。11.7.1获取窗口列表〖案例要求〗:获取当前已打开的窗口。〖知识要点〗:Caption〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 遍历窗口()'声明一个窗口型变量Dim win As Window, Item As Byte'遍历所有窗口For Each win In Windows'累加变量Item = Item + 1'将窗口标题导入单元格Cells(Item, 1) = win.CaptionNext winEnd Sub以上过程遍历所有窗口,将逐个提取窗口的标题,再导入到工作表 A 列。Excel VBA 程序开发自学通2020-2-26第 391页 /共 508页(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“遍历窗口”,在活动工作表中 A 列将罗列出所有窗口的标题,见图 11.141 所示:图 11.141获取窗口列表〖语法补充〗:(1)Window.Caption 属性它代表文档窗口标题栏中显示的名称。如果工作表已保存,则标题中包含后缀名,否则不包含后缀名,如图 11.141 中“Book3”。本例文件参见光盘:..\ 第十一章\遍历窗口.xlsm11.7.2确保随时打开工作簿都窗口最大化〖案例要求〗:对工作表密码保护,关闭时缩小窗口,重启后需要密码确认查看权限,如果密码正常则放大窗口,否则窗口最小化,无法查看数据。〖知识要点〗:WindowState〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub Auto_open()'如果有密码保护则退出程序IF ActiveWorkbook.ProtectWindows Then Exit Sub'将窗口最大化Windows(1).WindowState = xlMaximizedEnd Sub(3)保存并关闭工作簿,重新启动工作簿后,它总会窗口最大化。除非已对工作簿的窗口进行密码保护。〖语法补充〗:(1)Window.WindowState 属性返回或设置窗口的状态。包括以下三种状态:表 11-43 窗口状态列表名称xlMaximizedxlMinimizedxlNormal值-4137-4140-4143描述最大化最小化正常本例文件参见光盘:..\ 第十一章\确保每次打开都最大化.xlsmExcel VBA 程序开发自学通11.7.32020-2-26第 392页 /共 508页切换当前窗口的网格线、滚动条、标题与工作表标签〖案例要求〗:切换当前窗口的网格线、滚动条、标题与工作表标签,运行时可以隐藏,再次运行时则显示。〖 知 识 要 点 〗 : DisplayGridlines 、 DisplayHorizontalScrollBar 、DisplayVerticalScrollBar、DisplayHeadings、DisplayWorkbookTabs〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 切换()With ActiveWindow.DisplayGridlines = Not .DisplayGridlines '网络线.DisplayHorizontalScrollBar = Not .DisplayHorizontalScrollBar '水平滚动条.DisplayVerticalScrollBar = Not .DisplayVerticalScrollBar '垂直滚动条.DisplayHeadings = Not .DisplayHeadings '行标题和列标题.DisplayWorkbookTabs = Not .DisplayWorkbookTabs '工作表标签End WithEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“切换”,当前窗口中的网格线、滚动条、行列标题与工作表标签将会在显示与隐藏之间切换。〖语法补充〗:(1)Window.DisplayGridlines 属性表示网格线的显示状态。(2)Window.DisplayHorizontalScrollBar 属性表示水平滚动条的显示状态。(3)Window.DisplayVerticalScrollBar 属性表示垂直滚动条的显示状态。(4)Window.DisplayHeadings 属性表示行标题和列标题的显示状态。(5)Window.DisplayWorkbookTabs 属性表示工作簿标签的显示状态。本例文件参见光盘:..\ 第十一章\切换.xlsm11.7.4自由滚动窗口方便阅读工作表数据〖案例要求〗:当前工作表数据超过 1 屏,利及 VBA 实现每秒钟向下滚动 1 行,方便用户阅读;如果滚动到最后一行,那么再单执行则变成每秒钟向上滚动一行。〖知识要点〗:SmallScroll、VisibleRange〖实现步骤〗:(1)激活需要滚动的工作表,单击菜单【开发工具】\【插入】\【命令按钮(ActiveX控件)】,并在工作表中按下左键拖动,分别绘制两个命令按钮;(2)单击右键,从菜单中选择【查看代码】,从而工作表事件代码窗口;(3)在代码窗口输入以下代码:Dim aSub 表格每秒滚动一行()Dim i As Integer, Time_one As Single, Time_two As SingleTime_one = Timer: Time_two = TimerExcel VBA 程序开发自学通2020-2-26第 393页 /共 508页DoTime_one = Timer()Application.Wait (1)IF (Abs(Time_two - Time_one) > 1) ThenTime_two = Time_oneActiveWindow.SmallScroll DOWN:=ai=i+1DoEventsEnd IFLoop Until i = Cells(Rows.Count, 1).End(xlUp).Row - 2CommandButton1.Caption = VBA.IIF(a = 1, "向上滚动", "向下滚动")End Sub以上过程让当前窗口每秒钟滚动一行,而滚动的方向由变量 a 决定。Private Sub CommandButton1_Click()'如果已用区域不超过一屏显示则不执行程序IF Cells(Rows.Count, 1).End(xlUp).Row < ActiveWindow.VisibleRange.Row +ActiveWindow.VisibleRange.Rows.Count Then Exit Sub'在 B3 处锁定窗格,让两行行标题固定在上方ActiveWindow.FreezePanes = FalseRange("A3").SelectActiveWindow.FreezePanes = True'如果按钮标题是向下滚动则变量 a 赋值 1,并激活 A3,开始向下滚动表格IF (CommandButton1.Caption = "向下滚动") Thena=1[a3].SelectCommandButton1.Caption = "向上滚动"表格每秒滚动一行Else'否则直接滚动到最后一行,且该行显示在当前屏幕最底端ActiveWindow.SmallScroll DOWN:=-((ActiveWindow.VisibleRange.Rows.Count +ActiveWindow.VisibleRange.Row - 1) - (ActiveWindow.VisibleRange.Rows.Count +Cells(Rows.Count, 1).End(xlUp).Row))a = -1CommandButton1.Caption = "向下滚动"表格每秒滚动一行End IFEnd Sub以上过程是单击第一个按钮时执行的过程,它根据按钮的显示标题决定变量 a 的值,并改变按钮的标题。当变量 a 产生变化后,窗口滚动的方向也相应的变化。Private Sub CommandButton2_Click()CommandButton2.Caption = "暂停"EndEnd Sub以上过程用于暂停窗口滚动。(4)返回工作表,单击功能区菜单【开发工具】\【设计模式】,从而退出设计模式;(5)单击第一个按钮执行程序,如果当前工作表数据不超过一屏,则无任何反应;如果当前工作表数据超过一屏,那么程序会首先定位于最后一行,然后每秒钟向上滚动一行,中途可以单击第二个按钮中断程序;(6)当滚动到最顶端后,滚动会自动停止,且按钮的标题已自动修改为“向下Excel VBA 程序开发自学通2020-2-26第 394页 /共 508页滚动”。单击“向下滚动”按钮,窗口会每秒钟向下滚动一行,直到最后一个非空行。〖语法补充〗:(1)Window.SmallScroll 方法表示按行或按列滚动窗口内容。它的具体语法如下:表达式.SmallScroll(Down, Up, ToRight, ToLeft)它的四个可选参数含义如下:表 11-44Window.SmallScroll方法参数列表名称DownUpToRightToLeft描述将内容向下滚动的行数将内容向上滚动的行数将内容向右滚动的列数将内容向左滚动的列数其实也可以仅仅使用 Down 和 ToRight 两参数,因为可以对其赋负值来替代其它两个参数的功能。例如向下滚动 2 行可以下语句:ActiveWindow.SmallScroll DOWN:=2若向上滚动 10 行,仍然可以使用 DOWN 参数,改用负值即可:ActiveWindow.SmallScroll DOWN:=-10(2)Window.VisibleRange 属性用于返回一个 Range 对象,它代表显示在窗口或窗格中的单元格区域。即在当前窗口中能看到的区域,包括能看到部分的单元格。例如图 11.142 中第 8 行和第 E 列虽然仅仅能看到部分,它也属于 VisibleRange 区域。使用代码“MsgBox ActiveWindow.VisibleRange.Address”可以得到地址“$A$1:$E8$”:图 11.142利用 VisibleRange 属性获取可见区域地址(3)Window.FreezePanes 属性用于控制窗格的冻结状态。如果窗格被冻结,则该属性值为 True。本例文件参见光盘:..\ 第十一章\每秒钟滚动一行.xlsm11.7.5以当前单元格为基准拆分窗格〖案例要求〗:以当前单元格为基准拆分窗格。〖知识要点〗:SplitColumn、SplitRow〖实现步骤〗:(1)单击菜单【插入】\【模块】;Excel VBA 程序开发自学通2020-2-26第 395页 /共 508页(2)在模块代码窗口输入以下代码:Sub 以当前单元格为基准拆分窗格()With ActiveWindow.SplitColumn = ActiveCell.Column - 1.SplitRow = ActiveCell.Row - 1End WithEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“以当前单元格为基准拆分窗格”,当前窗口会以活动单元格为基准,将窗口拆分成四个。见图 11.143 所示:图 11.143以当前单元格为基准拆分窗格〖语法补充〗:(1)Window.SplitColumn 属性返回或设置将指定窗口拆分成窗格处的列号(拆分线左侧的列数)。(2)Window.SplitRow 属性返回或设置将指定窗口拆分成窗格处的行号(拆分线以上的行数)。本例文件参见光盘:..\ 第十一章\以当前单元格为基准拆分窗格.xlsm11.7.6计算活动单元格左边距〖案例要求〗:计算活动单元格的左边距,是指活动单元格到左边行标题的距离。代码需要通用于所有情况,包括冻结窗格时。〖知识要点〗:Panes〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 计算当前单元格左边距()With ActiveWindow.Panes'循环所有窗格(当冻结时有多个窗格)For i = 1 To .Count'如果活动单元格与第 i 个窗格存在交集IF Not Intersect(ActiveCell, .Item(i).VisibleRange) Is Nothing Then'如果是第一个窗格或者第三个窗格IF i = 1 Or i = 3 Then'结果为活动单元格的左边距减去当前窗格第一个单元格的左边距MsgBox ActiveCell.Left - .Item(i).VisibleRange(1, 1).LeftExcel VBA 程序开发自学通2020-2-26第 396页 /共 508页Else'结果第一个窗格的宽度加上活动单元格的左边距加减去当前窗格第一个单元格的左边距MsgBox- .Item(i).VisibleRange(1, 1).LeftEnd IFExit ForEnd IFNextEnd WithEnd Sub.Item(1).VisibleRange.Width+Target.LeftRange.Left 虽然可以计算单元格的左边距,然而它的 BUG 很多,它将隐藏单元格的宽度也计算进去,从而差生误差。本例采用 Panes 配合 VisibleRange 可以避免这个 BUG。以上过程对活动单元格在第一、第三窗格时采用一种计算方式,对活动单元格在第二、第四窗格时采用另一种计算方式。前者利用活动单元格的左边距(Left)减去窗格第一个单元格的左边距来实现忽略隐藏列的宽度;后者则在前者的计算基上加上第一个窗格中可见区域的宽度。(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“计算当前单元格左边距”,假设在工作表中窗格显示方式如图 11.144 所示,那么计算结果为 92.25,而直接用“ActiveCell.Left”计算则结果结果为 153.75,两者的差异正好是 A 列和 E 列两个隐藏列的宽度。图 11.144计算活动单元格左边距〖语法补充〗:(1)Window.Panes 属性返回一个 Panes 集合,该集合表示指定窗口中的所有窗格。(2)如果窗口没有冻结时,窗口中只有一个窗格;如果冻结,那么可能有四个窗格,也可能有两个窗格,由冻结的位置而定。本例文件参见光盘:..\ 第十一章\以当前单元格为基准拆分窗格.xlsm11.7.7计算活动单元格的屏幕位置〖案例要求〗:单元格的 Left 属性与 Top 属性是相对于工作表左边缘、上边缘的距离,以磅为单位。不管 Excel 的窗口如何移动、功能的位置如何变化都不影响该置。本例中演示单元格相对于屏幕的象素位置。〖知识要点〗:PointsToScreenPixelsX〖实现步骤〗:Excel VBA 程序开发自学通2020-2-26第 397页 /共 508页(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 屏幕位置()'如果选择单元格IF TypeName(Selection) = "Range" ThenWith ActiveWindow'将点)为单位转换为以屏幕像素(屏幕坐标)为单位MsgBox "上边距:" & .PointsToScreenPixelsX(.ActiveCell.Top) & Chr(10) _& "左边距:" & .PointsToScreenPixelsY(.ActiveCell.Left), 64, "屏幕座标"End WithEnd IFEnd Sub(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“屏幕位置”,如果当前选择了单元格,那么程序会弹出图 11.145 所示的提示,表示活动单元格的屏幕位置。选择同一个单元格时,其 Top 和 Left 总是不变化,不管如何移动或者缩放窗口,但其屏幕象素位置却会变化。图 11.145计算活动单元格的屏幕位置〖语法补充〗:(1)Window.PointsToScreenPixelsX 方法可将横向度量值由以点(文档坐标)为单位转换为以屏幕像素(屏幕坐标)为单位。返回转变后的度量值。本例文件参见光盘:..\ 第十一章\计算活动单元格的屏幕位置.xlsm11.7.8三种方式不显示零值〖案例要求〗:使用三种方式不显示零值,包括隐藏活动工作表中的所有零值、隐藏选区零值及删除所有零值。〖知识要点〗:DisplayZeros〖实现步骤〗:(1)单击菜单【插入】\【模块】;(2)在模块代码窗口输入以下代码:Sub 不显示零值()Dim Opt As ByteOn Error Resume NextStar:'让用户选择零值的处理方式Opt = Application.InputBox("输入 1:隐藏所有零值单元格" & Chr(10) & _"输入 2:隐藏选区零值" & Chr(10) & _"输入 3:删除所有零值", "隐藏零值", 1, , , , , 1)'超过 Byte 的范围则返回重新输入Excel VBA 程序开发自学通2020-2-26第 398页 /共 508页IF Err 0 Then GoTo StarSelect Case OptCase 1 '输入 1 就隐藏当前表所有零值ActiveWindow.DisplayZeros = FalseCase 2 '输入 2 则隐藏选区中的零值IF TypeName(Selection) = "Range" ThenSelection.NumberFormatLocal = "[=0]"""";G/通用格式"ElseMsgBox "请选择区域。", 64, "提示"End IFCase 3 '输入 3 则将零值替换成空白Cells.Replace 0, "", xlWholeEnd SelectEnd Sub(3)选回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“不显示零值”,将弹出图 11.146 所示对话框,让用户选择对零值的处理方式。如果选择1,那么当前表所有零值都会隐藏起来(编辑栏中可以看到,单元格中不显示),见图11.147 所示;如果选择 2,那么只隐藏选择区域的零值;如果选择 3 则删除所有当前工作表中的零值,单元格中和编辑栏中都不显示,见图 11.148 所示:图 11.146图 11.147选择零值处下方式隐藏零值图 11.148删除零值〖语法补充〗:(1)Window.DisplayZeros 属性表示零值的显示方式,则为属性值 True 则显示零值,否则隐藏零值,它的设置仅对当前工作表生效,其它工作簿或者工作表需要再设置。(2)本例中的三种方式各有优势。第一种方式对整个工作表生效,仅改变显示值,不改变实际值,随时可以撤消隐藏设置;第二种方法对可以对选定的任意区域设置隐藏,而且也方便取消零值的隐藏,但如果选择已有部分单元格有自定义数字格式将破坏原有设置;第三种方式删除所有零值单元格,无法还原。虽然可以定位空单元格,再输入“0”值,然而却可以将原来的空单元格也意外的加入“0”。本例文件参见光盘:..\ 第十一章\三种方法不显示零值.xlsm第十二章 Excel 的事件应用案例Excel 的事件可以让程序自动执行,或者基于某个条件下自动执行,不需要用户手工引导,这使事件过程在工作中得到了广泛的应用。在本书第八章中介绍了事件的基本知识,然后通过第九章、十章和十一章地学习,Excel VBA 程序开发自学通2020-2-26第 399页 /共 508页已经常握代码编写规则、常见语句的语法,以及对 Excel 的对象应用有了较深入地认识,那么现在足以编写 Excel 事件的实例应用程序了。本章对 Excel 事件将进行全面地高级应用案例演示,让读者加深对事件的理解及提升对象及事件的驾驭能力。本章要点: 应用程序事件案例 工作簿事件案例 工作表事件案例 ActiveX 控件事件案例12.1 应用程序事件案例应用程序级别的事件对所有工作簿、工作表和单元格都生效。编写应用程序级别事件时需要用到类模块的知识。12.1.1新工作簿环境设计〖案例要求〗:每次新建工作簿时,默认有 7 个工作表,第一个工作表名为“总表”,其它表名为分表。〖实现步骤〗:(1)新建工作簿,使用快捷键【Alt+F11】进入 VBE 界面;(2)单击菜单【插入】\【模块】;(3)在模块中录入以下代码:Dim xlapp As New appevents'声明一个对象Sub auto_open() '工作表开启时执行Set xlapp.app = Application '将应用程序赋与对象变量End SubSub auto_close()Set xlapp.app = Nothing'释放变量End Sub(4)单击菜单【插入】\【类模块】;(5)单击快捷键【F4】打开属性窗口,将类模块的默认名“类 1”修改为“appevents”;(6)在类模块中输入以下代码:Public WithEvents app As Application '声明可触发事件的对象变量Private Sub app_NewWorkbook(ByVal Wb As Workbook) '声明应用程序事件Dim i As ByteWb.Sheets.Add , , 7 - Sheets.Count '创建 4 个工作表(默认有 3 个)Wb.Sheets(1).Name = "总表" '将第一个命名为总表For i = 2 To Wb.Sheets.Count '从第二开始 直到最后一个Wb.Sheets(i).Name = "分表" & i - 1 '改名为“分表”加编号NextEnd Sub在本过程的代码中,首先通过“Sheets.Count”计算新工作簿的默认工作表数量,然后通过“Sheets.Add”添加相应的新工作表,使其加上原来的工作表数量后等于 7。最后再对每个工作表重命名。其中事件过程的参数 Wb 代码新建的工作簿对象。(7)关闭 VBE 界面返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,执Excel VBA 程序开发自学通2020-2-26第 400页 /共 508页行“Auto_open”宏;(8)单击按钮新建一个工作簿,可以发现它将带有 7 个工作表,且命名完全按照程序的思路进行。见图 12.1 所示:图 12.1利用应用程序事件设置新工作簿环境本例文件参见光盘:..\ 第十二章\利用事件设置新工作簿的工作表.xlsm应用程序的事件过程需要用到类模块相关的代码。关于类的知识将在本书第 25章进行介绍,读者也可以学习第 25 章后再继续应用程序级别事件的编写。不过在应用程序事件过程涉及的类的知识不多,不管其它任何事件,都可以从本例代码中做小小修改即可。12.1.2打开任意工作簿时全自动备份〖案例要求〗:打开任意工作簿时全自动备份,备份文件路径与原文件一致,名称为原名添加备份时间。〖实现步骤〗:(1)新建工作簿,使用快捷键【Alt+F11】进入 VBE 界面;(2)单击菜单【插入】\【模块】;(3)在模块中录入以下代码:Dim xlapp As New appevents'声明一个对象Sub auto_open() '工作表开启时执行Set xlapp.app = Application '将应用程序赋与对象变量End SubSub auto_close()Set xlapp.app = Nothing'释放变量End Sub(4)单击菜单【插入】\【类模块】;(5)单击快捷键【F4】打开属性窗口,将类模块的默认名“类 1”修改为“appevents”;(6)在类模块中输入以下代码:Public WithEvents app As Application '声明可触发事件的对象变量'开启工作簿时在原路径下备份,备份文件为原加加备份时间Private Sub app_WorkbookOpen(ByVal Wb As Workbook)Wb.SaveCopyAs Wb.Path & "\备份(" & Format(Now(), "hhmmss)") & Wb.NameEnd Sub本过程可以实现打开任意工作簿时将文件备份。(7)关闭 VBE 界面返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,执行“Auto_open”宏;Excel VBA 程序开发自学通2020-2-26第 401页 /共 508页(8)打开一个工作簿,假设为 C 盘下“生产表.xlsx”,那么该工作簿开启后在 C盘中会立即产生该文件的备份,如果修改错误,且因为步骤太多无法撤消操作或者停电等等意外原因破坏文件,那么可以从备份文件中还原所有数据。图 12.2开启工作簿时自动备份本例文件参见光盘:..\ 第十二章\开启任意工作簿时备份.xlsm12.2 工作簿事件案例工作簿级别的事件对当前工作簿中所有工作表和单元格都生效,对其它工作簿不产生任何影响。12.2.1新建工作表时自动设置页眉〖案例要求〗:对当前工作簿中任何工作表添加页眉,页眉内容包括当前日期和工作表当前页、总页数。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在窗口中录入以下工作簿级“Workbook_NewSheet”事件的代码:Private Sub Workbook_NewSheet(ByVal Sh As Object)With ActiveSheet.PageSetup '设置页面.LeftHeader = "&D"'页眉左边插入日期.CenterHeader = ""'中间空白.RightHeader = "第&P 页总&N 页" '右边显示页数End WithEnd Sub本代码对所有新建的工作表都生效,在创建工作表时默认生成页眉。在代码中包括设置页眉的左、中、右三个位置的内容。其中事件过程的参数 Sh 代表新建工作表对象。(4)以上过程仅仅对新工作表生效,工作簿已经存在的工作表暂无法生成页眉。所以需要再加入以下代码:Private Sub Workbook_BeforePrint(Cancel As Boolean)With ActiveSheet.PageSetup '设置页面.LeftHeader = "&D"'页眉左边插入日期.CenterHeader = ""'中间空白.RightHeader = "第&P 页总&N 页" '右边显示页数Excel VBA 程序开发自学通2020-2-26第 402页 /共 508页End WithEnd Sub本事件过程代码表示打印工作前之前设置页眉。其中事件过程的参数 Cancel 用于控制工作表是否可以打印,如果对其赋值 Flase 则禁止用户打印工作表数据。(5)关闭 VBE 窗口返回工作表界面,将任意工作表进行打印预览,都可以发现其页眉已经自动设置 OK。也可以通过功能区【视图】中的【页面布局】来预览设置的页眉,见图 12.3 所示:图 12.3利用工作簿事件设置的页眉预览本例文件参见光盘:..\ 第十二章\自动设置工作表页眉.xlsm12.2.2禁止缩小工作簿窗口〖案例要求〗:工作簿窗口默认是最大化,通过工作簿事件禁止终端用户修改工作簿窗口的大小,永远保持最大化状态。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:Private Sub Workbook_WindowResize(ByVal Wn As Window)Wn.WindowState = xlMaximizedMsgBox "禁止缩小工作区窗口", 64, "提示"End Sub以上过程在用户改变工作簿窗口大小时执行,代码表示只要改变窗口的大小就将它自动还原为最大化,且提示“禁止缩小工作区窗口”。其中事件过程的参数 Wn 代表窗口对象。(4)返工作表界面,单击右上角的“窗口最小化”和“还原窗口”都已经失效,而且弹出图 12.5 所示的提示信息。注意是下面的三个按钮控制工作簿窗口的缩放及关闭,上面的三个按钮是应用控制 Excel 应用程序的。Excel VBA 程序开发自学通图 12.42020-2-26“还原窗口”按钮第 403页 /共 508页图 12.5修改窗口大小时的提示本例文件参见光盘:..\ 第十二章\禁止修改工作簿窗口大小.xlsm12.2.3未汇总则禁止关闭工作簿〖案例要求〗:工作簿中有三个工作表,两个存放生产数据,一个名为“汇总表”,在“汇总表”的 B1 用于存放所有车间的产量总和。要求利用事件过程实现未汇总就禁止关掉工作簿,从而避免数据录入不完整及忘记汇总数据。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:Private Sub Workbook_BeforeClose(Cancel As Boolean) '关闭工作簿前执行IF Len(Sheets("汇总表").Range("B1")) = 0 Then '如果汇总单元格为空白Cancel = True'禁止保存MsgBox "请先汇总生产数据,否则禁止关闭", 64, "提示" '提示用户End IFEnd Sub以上过程在关闭工作簿之前执行,程序会检查汇总数据存放单元格是否空白,如果空白则禁止保存。事件过程的参数 Cancel 用于指定是否可以保存工作簿,赋值为True 时允许保存,赋值为 False 时禁止保存。(4)返回工作表界面,在未汇总的状态下单击 Excel 的关闭按钮,将弹出图 12.6所示提示。图 12.6未汇总则禁止关闭本例文件参见光盘:..\ 第十二章\未汇总则禁止关闭工作簿.xlsmExcel VBA 程序开发自学通12.2.42020-2-26第 404页 /共 508页新建工作表时以当前时间命名〖案例要求〗:新建工作表时以当前时间命名,时分秒形式。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:Private Sub Workbook_NewSheet(ByVal Sh As Object)'对新建的工作表命名,以当前日间格式化成时分秒后做为命称Sh.Name = Format(Now(), "H 时 M 分 S 秒")End Sub(4)返回工作表界面,新建一个工作表,那么该表的默认名称时当前系统时间,见图 12.7 所示:图 12.7新建工作表时以当前时间命名本例文件参见光盘:..\ 第十二章\新建工作表时以当前时间命名.xlsm12.2.5关闭工作簿前删除多余工作表〖案例要求〗:关闭工作簿前删除所有没有用的空表。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:'声明工作簿事件,在关闭工作簿时执行Private Sub Workbook_BeforeClose(Cancel As Boolean)Dim Item As Integer'遍历工作表For Item = Worksheets.Count To 1 Step -1'当删除到最后一个工作表时不管是否空表都停止IF Sheets.Count = 1 Then End'关闭警告提示,避免删除工作表时中断Application.DisplayAlerts = False'如果工作表是空表(没有数据和图形)则删除IF WorksheetFunction.CountA(Worksheets(Item).Cells) = 0 And Worksheets(Item).Shapes.Count = 0 ThenExcel VBA 程序开发自学通2020-2-26第 405页 /共 508页Worksheets(Item).DeleteEnd IF'还原提示Application.DisplayAlerts = TrueNext Item'保存工作簿并关闭Me.Close SaveChanges:=TrueEnd Sub以上过程从最后一个工作表开始,向第一个工作表进行循环检查,如果既没有数据也没有 Shapes 对象则删除,但是当只一个工作表时将它保留,因为工作簿不允许没有一个工作表。(4)保存代码关闭工作簿,当重新打开后可以发现所有未使用过的工作表都已经删除。本例文件参见光盘:..\ 第十二章\关闭工作簿前删除多余工作表.xlsm12.2.6除了月底禁止打印总表〖案例要求〗:除了本月底最后一天禁止打印总表,其它工作表不限制。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:'声明工作簿事件,打印工作表时触发Private Sub Workbook_BeforePrint(Cancel As Boolean)'如果当前表是“总表”IF ActiveSheet.Name = "总表" Then'如果日期是本月最后一天IF Date = WorksheetFunction.EoMonth(Date, 0) Then'允许打印Cancel = FalseElse'则否禁止打印Cancel = TrueEnd IFEnd IFEnd Sub以上过程首先检查当前表名是否“总表”,表示不限制其它工作表;然后检查今日日期是否等于本月最后一天,如果是则将工作簿事件过程的参数 Cancel 赋为 False,表示允许打印,否则禁止打印,连预览也不行。其中工作表函数 EoMonth 属于 Excel 2007 的内部集成函数,可以直接使用;但Excel 2003 中需要加载分析工具库才可以使用,见图 12.8 所示。如果加载宏对话框中的列表是空白,没有任何加载宏,那么表示安装时未安装完整,那么可以在代码中做调整。例如代码“WorksheetFunction.EoMonth(Date, 0)”的功能可以使用以下代码替代,而且它是 Excel 2003 和 Excel 2007 通用的:Day(VBA.DateSerial(Year(Date), Month(Date) + 1, 0))Excel VBA 程序开发自学通2020-2-26第 406页 /共 508页图 12.8 加载分析工具库(4)返回工作表界面,进入“总表”,如果今天不是月末最后一天,那么无法预览或者打印工作表,而其它日期可以打印。本例文件参见光盘:..\ 第十二章\除了月底禁止打印总表.xlsm12.2.7调整窗口大小时报告可见区域行列数〖案例要求〗:调整窗口大小时报告可见区域行列数。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:'声明工作簿事件,在改变窗口大小时触发Private Sub Workbook_WindowResize(ByVal Wn As Window)'获取可见区或的行数与列数MsgBox "可见行:" & Wn.VisibleRange.Rows.Count & Chr(10) & _"可见列:" & Wn.VisibleRange.Columns.Count, 64, "当前窗口行列数"End Sub(4)返回工作表界面,当按下缩小、放大、还原按钮,或者拖动方式改变窗口大小时,Excel 会弹出可见区域行列数的提示,见图 12.9 所示:图 12.9 可见区域行列数本例文件参见光盘:..\ 第十二章\调整窗口大小时报告可见区域行列数.xlsmExcel VBA 程序开发自学通12.2.82020-2-26第 407页 /共 508页禁止切换到其它工作簿〖案例要求〗:禁止切换到其它工作簿。〖实现步骤〗:(1)按下快捷键【Alt+F11】打开 VBE 界面;(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook 进入工作簿事件代码窗口;(3)在代码窗口中录入以下工作簿事件过程代码:'声明工作簿事件,激活当前工作簿以外的窗口时触发此事件Private Sub Workbook_WindowDeactivate(ByVal Wn As Window)'激活当前工作簿的窗口Wn.ActivateEnd Sub(4)返回工作表界面,单击快捷键【Ctrl+N】建立新工作簿,但是当前工作簿的窗口永远处于激活状态;(5)从状态栏单击其它工作簿的窗口,不会产生任何反应,工作簿事件过程已禁止切换到其它工作簿窗口。本例文件参见光盘:..\ 第十二章\禁止切换到其它工作簿.xlsm12.3 工作表事件案例工作表事件仅仅对代码所有工作表产生作用。本节对工作表事件进行详细的案例介绍。12.3.1选择单元时在状态栏提示地址〖案例要求〗:选择当前工作表任何区域时在状态栏提示选区地址。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)Application.StatusBar = "当前选区是:" & Selection.AddressEnd Sub以上事件过程在选择任单元格时执行,StatusBar 代表状态栏,可以对其赋值,但赋值的字符受长度需要受限,因为状态栏只能显示有一行。事件过程中的参数 Target代表选区的单元格对象。(3)返回工作表界面,选择任意单元格后,在状态栏都会显示其地址。如果选择多个区域也可以将多区域的地址完整显示出来,见图 12.10 所示。使用工作表事件时不能跨表操作,即同时选择多个工作表的单元格区域。Excel VBA 程序开发自学通2020-2-26图 12.10第 408页 /共 508页在状态栏显示选区地址Target 和 ActiveCell、Selection 三个都代表单元格,它们有相同点,也有不同点。相同点是用在“Worksheet_SelectionChange”事件中,且选择单个单元格时,它们三个方法都可以用于表示当前选择的单元格,此时没有任何分别。不同点是:Target 是VBA 内部的预定义对象变量,只仅仅用于事件过程中,可以用于表示当前选择的区域;而 Selection 可以用在任何模块中,用于用于表示当前选择的区域(有时也可以表示其它的对象,如图形对象等等);Activecell 可以在任何模块中使用,它用于表示活动单元格,而活动单元格仅仅一个,即使选择了多个区域。这代表整个选区的 Selection与 Target 是不同的。本例文件参见光盘:..\ 第十二章\在状态栏显示选区地址.xlsm12.3.2快速录入出勤表〖案例要求〗:在图 12.11 所示出勤表的 B2:H11 区域中单击可以产生“迟到”,双击可以产生“早退”,而右击时产生“请假”,从而提升录入效率。图 12.11出勤表〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)IF Not Application.Intersect(Target(1), Range("B2:H11")) Is Nothing ThenExcel VBA 程序开发自学通2020-2-26第 409页 /共 508页IF Target.Count = 1 Then Target = "迟到"End IFEnd Sub以上事件过程用于单击单元格时产生“迟到”,且只对 B2:H11 区域生效,其它区域不受任可影响,而且只有选择单个单元格时才生效。其中参数事件过程的参数 Target代表选择的区域。如果使用 Target(1)则表示选区左上角的单元格。代码中的 Intersect 用于获取两个区域的重叠区域。本例中借用 Intersect 可以判断选区 Target 是否在 B2:H11 区域,如果单击其它区域即 Intersect 获取的重叠区域是Nothing,那么程序就不执行。(3)继续输入以下事件过程代码:Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)IF Not Application.Intersect(Target, Range("B2:H11")) Is Nothing ThenTarget = "早退"End IFEnd Sub以上事件过程用于双击单元格时输入“早退”,同样只对 B2:H11 区域生效。(4)继续输入以下事件过程代码:Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)IF Not Application.Intersect(Target, Range("B2:H11")) Is Nothing ThenCancel = TrueTarget(1) = "请假"End IFEnd Sub以上事件过程用于右键单击单元格时,在单元格中产生“请假”,仅对 B2:H11 区域生效。其中“Cancel = True”表示取消右键菜单,而在其它区域单击右键时则可以恢复右键菜单。(5)返回工作表,并单击 B2:H11 区域中任意单元格即可实现“标识迟到”的功能,而选择超过 1 个单元格时则忽略;再在 B2:H11 区域任意单元格双击、右键单击都可以完成预设的功能。本实例可以实现快速录入不超过三项的出勤状记录,如果还需要加入病假、年假、公假等等,那么只能通加自定义菜单来完成,参阅本书第 23 章。本例文件参见光盘:..\ 第十二章\快速录入出勤表.xlsm12.3.3建立只能使用一次的超链接〖案例要求〗:让工作表中的超级链接只能使用一次,当用户单击链接(即链接的任务完成)后自动消失。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)Target.DeleteEnd Sub以上事件过程用于在工作表中发生超级链接事件时删除超链接。事件过程中Target 代表当前单击的超级链接,利用 Delete 方法可以清除超链接。Excel VBA 程序开发自学通2020-2-26第 410页 /共 508页(3)反回工作表中,在 A1 单元格录入一个网址 http://baidu.com,Excel 会自动为其加上超级链接功能,见图 12.12 所示;(4)单击 A1 单元格,此时可以打开百度网页,而 A1 单元格的链接瞬间被删除,链接的任务已经完成。图 12.12网址链接本例文件参见光盘:..\ 第十二章\建立任务完成后自动删除的超链接.xlsm12.3.4让 A1 的日期单击更新〖案例要求〗:单元格 A1 存放了工作表数据的日期,体现当前资料的建立日期,现需在需要的时候可以单击更新资料日期,且必须显示星期几。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)IF Target(1).Address = "$A$1" Then Target(1) = Format(Date, "DDDD yyyy-mm-dd")End Sub以上事件过程代码可以实现单击 A1 单元格时更新 A1 的日期,且日期格式为星期加年月日。如果用户单击其它区域将无任何反应。过程中的 Target 代表当前选区,这个区域可以是单个单元格,也可以是多个单元格,甚至是多个区域。而程序要求仅仅对 A1 单个单元格生效,那么通常使用索引号 1 来限制操作的作用对象是区域中左上角单元格。(3)返回工作表界面,单击 A1 以外的任意单元格,无任何反映;而单击 A1 则呈现星期加日期,见图 12.13 所示。图 12.13 利用工作表事件产生日期本例文件参见光盘:..\ 第十二章\只在 A1 单击更新日期.xlsm12.3.5在状态栏显示选区的字母、数字、汉字个数〖案例要求〗:选择单元格中任意区域时,在状态栏显示选区中的字符个数,以及字母个数、数字个数和汉字个数。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查Excel VBA 程序开发自学通2020-2-26第 411页 /共 508页看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)Dim 字符 As String, 字符数 As Long, 汉字 As Long, 字母 As Long, 数字 As Long,其它符号Dim rng As Range, Item As Integer, rngg As Range'遍历选区,为了防止用户全选或者选列整列时造成的不必要的循环,使用 Intersect 方法让程序在非空区循环IF Intersect(Selection, ActiveSheet.UsedRange) Is Nothing Then EndFor Each rng In Intersect(Selection, ActiveSheet.UsedRange)'汇总所有单元格的字符数字符数 = 字符数 + Len(rng)'逐一提取每个单元格中的每个字符For i = 1 To Len(rng)字符 = Mid(rng, i, 1)'一到龥之间的都是汉字(仅适用于简体系统)IF 字符 Like "[一-龥]" = True Then汉字 = 汉字 + 1'汉字累加ElseIF 字符 Like "[a-zA-Z]" = True Then字母 = 字母 + 1'字母累加ElseIF 字符 Like "[0-9]" = True Then数字 = 数字 + 1'数字累加Else其它符号 = 其它符号 + 1End IFNextNextApplication.StatusBar = "字数:" & 字符数 + 0 & "个,其中:" & "汉字:" & 汉字 + 0 &"个," & _"字母:" & 字母 + 0 & "个," & "数字:" & 数字 + 0 & "个," & "其它符号:" & 其它符号 + 0 & "个"End Sub(3)返回工作表界面,选择空白区域,状态栏无任何反应;如果选择已用区域中的非空单元格,在状态栏会提示字数、汉字个数、字母个数、数字个数和其它符号的个数。其它符号指标点、空格、片假名等等,见图 12.14 所示。图 12.14在状态栏显示选区字数本例文件参见光盘:..\ 第十二章\选区字符统计.xlsm12.3.6实时监控单元格每一次的编辑数据与时间〖案例要求〗:保存 B 列每个单元格的修改记录,包括修改的数据和时间。Excel VBA 程序开发自学通2020-2-26第 412页 /共 508页〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:'声明工作表事件,当编辑单元格触发事件Private Sub Worksheet_Change(ByVal Target As Range)IFTarget.Column2OrTarget.Rows.Count=Rows.CountOrTarget.Columns.Count = Columns.Count Then Exit SubApplication.ScreenUpdating = FalseDim rng As Range, tim As String'记录当前时间,包括月日时分秒tim = Format(Now(), "m 月 d 日 hh:mm:ss")'遍历 B 列中所有编辑的单元格(当用户利用 Ctrl+Enter 方式批量输入时可以批量添加批注)For Each rng In Target(1, 1).Resize(Target.Rows.Count, 1)'如果已经有批注IF Not rng.Comment Is Nothing Then'在原来的批注后添加时间及内容(适合数据类的记录,如果单元格中是长长的文本,没有监控的必要)rng.Comment.Text rng.Comment.Text & Chr(10) & tim & ": " & IIF(rng = "", "【清空】", rng)Else'否则直接添加批注rng.AddComment tim & ": " & IIF(rng = "", "【清空】", rng)End IF'显示批注rng.Comment.Visible = Truerng.Comment.Shape.Select'自动调整大小Selection.AutoSize = Truerng.Comment.Visible = FalseNextApplication.ScreenUpdating = TrueEnd Sub以上事件过程在编辑单元格或者利用快捷键【Ctrl+Enter】批量编辑单元格时触发。过程首先查检单元格是否存在批注,如果没有则添加当前日期与单元格内容;如果有则在原字符串之下一行记录当前时间和内容。最后将批注的外框设定为自动适应批注内容。(3)返回工作表界面,在 A 列输入收支项目,在 B 列输入金额,那么 B 列的任意单元格的编辑记录都会在批注中体现出来,见图 12.15 所示:图 12.15记录当前输入的数据及时间(4)删除 B3 的值,然后再查看 B3 的批注,它保留了第一次输入的值-1250,让用户可以知道所有的编辑记录及每次的编辑时间。如果是删除数据则记录的是“【清Excel VBA 程序开发自学通2020-2-26第 413页 /共 508页空】”,见图 12.16 所示。程序员也可以根据需求修改删除数据时的显示字符串;图 12.16清空单元格时记录时间及保留以前的记录(5)选择 B4:C6 区域,输入数字 400 后按下快捷键【Ctrl+Enter】表示在多单元格同时输入数据,然后查看批注,可以发现仅仅 B 列的单元格会按要求记录时间的内容,其它列忽略。见图 12.17 所示:图 12.17多单元格输入时倮留 B 列的编辑记录本例文件参见光盘:..\ 第十二章\在批注中存放所有修改记录.xlsm12.3.7利用数字简化公司名输入〖案例要求〗:某公司有五个客户,分别为“广东长风汽车有限制公司”、“湖南衡大塑胶生产厂”、“江苏天信集团”、“珠海电信公司”和“北京天虹印刷厂”,在工作表 A 列中希望可以通过数字 1、2、3、4、5 来完成五个常用公司名称的录入工作。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:'声明工作表事件,当编辑单元格时触发事件Private Sub Worksheet_Change(ByVal Target As Range)'仅仅对第一列生效IF Target.Column = 1 Then'仅在单个单元格编辑时进行替换IF Target.Count > 1 Then Exit SubSelect Case TargetCase 1Target = "广东长风汽车有限制公司"Case 2Target = "湖南衡大塑胶生产厂"Case 3Target = "江苏天信集团"Case 4Target = "珠海电信公司"Case 5Target = "北京天虹印刷厂"Excel VBA 程序开发自学通2020-2-26第 414页 /共 508页End SelectEnd IFEnd Sub以上事件过程限制 A 列输入数字 1、2、3、4、5 才进行替换,其它区域忽略。(3)返回工作表界面,在 A2 单元格输入 1,见图 12.18 所示。当单击回车键后,单元格中的 1 将转换成“广东长风汽车有限制公司”,而且是永久性地转换,不能还原为 1,见图 12.19 所示:图 12.18在 A 列单个单元格输入 1图 12.19将 1 替换成广东长风汽车有限制公司本例文件参见光盘:..\ 第十二章\利用数字简化公司名输入.xlsm12.3.8录入数据时自动跳过带公式的单元格〖案例要求〗:图 12.20 是一个产量统计表,其中 D 列、F 列和 G 列分别利用公式计算率品数、不良率和良品率。现要求录入数据时,总是自动定位于下一个待输入数据的单元格,不需要手工移动方向键。即 C2 输入数据后,自动选择 E2;E2 输入数据后自动选择 H2;H2 输入数据后自动选择 A3,从而实现快速录入数据。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Private Sub Worksheet_SelectionChange(ByVal Target As Range)'仅对单选时生效IF Target.Count = 1 Then'设定单击回车键时向右移动Application.MoveAfterReturnDirection = xlToRight'当选择 I 列时返回 A 列IF Target.Column = 9 Then Target.Offset(1, -8).Select'如果有公式则选择右边一列IF Target.HasFormula Then Target.Offset(0, 1).SelectEnd IFEnd Sub以上事件在选择单元格时触发,当录入数据并单击回车键时,光标自动向右移动。如果右边一个单元格有公式,则表示不用输入数据,自动定位于它的右边一个单元格,如果该单元格仍然有公式则再选择右边的单元格,直至选择不包含公式的单元格为止;如果当前选择单元格是 I 列,那么自动跳转到 A 列下一行第一个单元格,从而实现自动定位。(3)返回工作表界面,在 B3 输入产量,活动单元格成为 C3。当 C3 输入废品数量后,活动单元格成为 E3,因为 B3 有公式。当 E3 输入数据后活动单元格成为 H3。Excel VBA 程序开发自学通2020-2-26第 415页 /共 508页当 G3 输入操作员姓名后,活动单元格将成为 A4.图 12.20D 列、F 列和 G 列带有公式的产量表虽然保护单元格也可以实现本例中相同的功能,但相对于 VBA 事件的灵活性则差一些,而且保护方式会带来一些后遗症,例如未保护区域无能合并、工作表中不能插入行等等。本例文件参见光盘:..\ 第十二章\自动跳过公式区.xlsm12.3.9在工作表的标题行禁用左、右键〖案例要求〗:工作表中有两个行标题和一个列标题,现要求在该标题行和标题列中禁止使用左右键,从而避免误删除标题。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Dim rng As Range'声明事件过程,在选择单元格时触发事件Private Sub Worksheet_SelectionChange(ByVal Target As Range)'如果选区行号大于 2 而且列号大于 1IF Target.Row > 2 And Target.Column > 1 ThenSet rng = Target '将选区赋与变量 RngElse'选择上一次选择的区域 RngIF rng Is Nothing Then Exit Sub Else rng.SelectEnd IFEnd Sub'声明事件过程,在右键单击单元格时触发Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)'如果选区行号大于 2 而且列号大于 1IF Target.Row > 2 And Target.Column > 1 ThenCancel = False'允许使用右键ElseCancel = True'禁止使用右键End IFEnd Sub第一个事件过程中,如果选择了非标题区域,则记录当前选区地址,如果选择了标题区,则返回上一次所选择的单元格区域,从而实现禁用左右。第二个事件过程直接对参数 Cancel 赋值为 True 来禁用右键。(3)返回工作表中,选择 B10 然后再选择 A10,那么程序会自动将活动单元格还原为 B10,因为 A 列禁用左键。如果测试第一行和第二行也有同样效果。Excel VBA 程序开发自学通2020-2-26第 416页 /共 508页本例文件参见光盘:..\ 第十二章\行列标题禁用左右键.xlsm12.3.10对选择区域进行背景着色〖案例要求〗:Excel 2007 的选区非常不明显,在输入和查看数据时不方便。本例实现选择单元格时突出显示指定的区域。如果选择单元格,则同时突出单元所在的行与列;如果选择区域,则突出区域本身。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:'声明事件过程,当选择单元格时触发Private Sub Worksheet_SelectionChange(ByVal Target As Range)On Error Resume Next'删除可能存在的条件格式(第一次单击时没有“行”“列”“区域”三个名称)[行].FormatConditions.Delete[列].FormatConditions.Delete[区域].FormatConditions.Delete'定义三个名称Target.EntireRow.Name = "行"Target.EntireColumn.Name = "列"Target.Name = "区域"'如果选择一个单元格对行与列添加条件格式背景IF Target.Count = 1 Then'对当前行添加条件格式With [行].EntireRow.FormatConditions.Delete.Add xlExpression, , "TRUE"'指定当前行条件格式中的背景色.Item(1).Interior.ColorIndex = 14End With'对当前列添加条件格式With [列].EntireColumn.FormatConditions.Delete.Add xlExpression, , "TRUE"'指定当前列条件格式中的背景色.Item(1).Interior.ColorIndex = 14End WithElse'当选择多单元格时则对选区添加条件格式背景色With [区域].FormatConditions.Delete.Add xlExpression, , "TRUE".Item(1).Interior.ColorIndex = 14End WithEnd IFEnd Sub以上过程首先删除三个名称“行”、“列”与“区域”所代码表区域的条件格式,然后重新定义名称,并根据当前选区的单元格个数添加不同的条件格式。条件格式中Excel VBA 程序开发自学通2020-2-26第 417页 /共 508页可以设置单元格背景色,也可以是单元格边框,甚至字体颜色,读者可以根据需求修改。(3)返回工作表界面,单击 D3,那么 D 列和第 3 行都会通过背景颜色来突出显示,见图 12.21 所示:图 12.21突出当前行与当前列(4)选择 B2:E5,那么 B2:E5 区域将会突出显示,见图 21.22 所示:图 12.22突出当前选区本例文件参见光盘:..\ 第十二章\背景着色.xlsm12.3.11适用于指定区域的自动更正〖案例要求〗:Excel 的自动更正默认对当前工作表簿所有单元格生效,现需在指定的区域中实现符号“>”自动更正成“→”。而且区域由用户随意指定。〖实现步骤〗:(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;(2)在代码窗口中录入以下工作表事件过程代码:Sub 选择区域()Dim address As StringOn Error Resume Next'如果选择单元格则获取选区地址,否则获取整个工作表中单元格地址IF TypeName(Selection) = "Range" Thenaddress = Selection.address(0, 0)Elseaddress = Cells.address(0, 0)End IF'让用户选择允许自动更正的区域,默认地址为变量 Address 所代表的区域Excel VBA 程序开发自学通2020-2-26第 418页 /共 508页'将后将该区域存入辅助单元格(在 2003 中是 IV1,2007 中是 XFD1)Cells(1, Columns.Count) = Application.InputBox(" 请 选 择 需 要 自 动 更 正 的 区 域 " &Chr(10) _& "如果想取消自动更正则按“取消”", "选择区域", address, , , , , 8).address'如果有错误(单击了“取消”)则将变量 Rng 赋值为 nothingIF Err 0 Then Cells(1, Columns.Count) = ""End Sub以上过程用于指定允许自动更正的区域,且将地址存在第一行最后一个单元格,可以随时手动修改该地址。' '声明过事件过程,编辑单元格时触发事件Private Sub Worksheet_Change(ByVal Target As Range)'防错,当用户在辅助区输入不规范的地址时可以不中断程序On Error Resume Next'如果辅助单元格是空白则结束过程IF Cells(1, Columns.Count) = "" Then End'如果当前选区不在指定的区域也结束过程IF Intersect(Target, Range(Cells(1, Columns.Count))) Is Nothing Then End'对编辑区域与指定的 Rng 区域重叠区域进行更正Intersect(Target, Range(Cells(1, Columns.Count))).Replace ">", "→", xlPartEnd Sub以上事件在编辑单元格时触发。如果在指定的区域以外编辑单元格或者指定区域为 Nothing 时,那么自动结束过程;否则将区域中的符号“>”替换成“→”。(3)返回工作表界面,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行程序“选择区域”。因代码输入在工作表事件代码窗口中,Excel 将会把工作表名与宏名一起罗列在“宏”对话框中。执行过程时会弹出图 12.23 所示对话框,让用户指定允许自动更正的区域。假设选择 B 列,那么在单元格输入符号“>”时会自动转换成“→”。而且因代码中使 Replace 的第三参数使用了 xlPart,表示近似匹配,那么单元格中输入“方向>”,那么程序同样也会将字符串中的“>”符号转换成“→”。而在其它任何区域输入“>”将会忽略;(4)如果在跨列区域输入“>”,那么仅仅对 B 列进行替换成。例如 B1:C3 中同时输入“>”,程序仅仅替换 B 列中的“>”;图 12.23图 12.24选择允许自动更正的区域跨列输入时替换 B 列的“>”(5)删除第一行最后单元格中人地址,或者重新执行过程“选择区域”,并单击取消按钮,那么 B 列的自动更正功能立即取消。本例文件参见光盘:..\ 第十二章\在指定区域实现自动更正.xlsmExcel VBA 程序开发自学通2020-2-26第 419页 /共 508页12.4 ActiveX 控件事件案例ActiveX 控件包括近百个控件,用户还可以自定义后缀名为“.ocx”的控件。ActiveX控件可以强化表格与 VBA 代码之间交互功能,而且它比窗体控件更强大的地方在于它支持事件。12.4.1鼠标移过时切换按钮颜色〖案例要求〗:网页中可可鼠标移过时切换按钮颜色或者文字颜色,本例实现对工作表中的命令按钮完成相同功能:鼠标指向的按钮蓝色背景显示,其它按钮则灰色背景显示。〖实现步骤〗:(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【命令按钮(ActiveX 控件)】,并在工作表中按下左键拖动,绘出一个按钮;(2)按下 Ctrl 键的同时利用左键拖动按钮,从而复制一个相同的按钮;(3)再次复制一个按钮;(4)在任意按钮上单击右键,选择菜单【查看代码】进入工作表事件代码窗口;(5)删除默认产生的代码,并输入以下代码:'第一个按钮的鼠标移过事件Private Sub CommandButton1_MouseMove(ByVal Button As Integer, ByVal Shift AsInteger, ByVal X As Single, ByVal Y As Single)'第一个按钮的背景色为蓝色,其余两个为灰色CommandButton1.BackColor = &HFF8080CommandButton2.BackColor = &HC0C0C0CommandButton3.BackColor = &HC0C0C0End Sub'第二个按钮的鼠标移过事件Private Sub CommandButton2_MouseMove(ByVal Button As Integer, ByVal Shift AsInteger, ByVal X As Single, ByVal Y As Single)'第二个按钮的背景色为蓝色,其余两个为灰色CommandButton2.BackColor = &HFF8080CommandButton1.BackColor = &HC0C0C0CommandButton3.BackColor = &HC0C0C0End Sub'第三个按钮的鼠标移过事件Private Sub CommandButton3_MouseMove(ByVal Button As Integer, ByVal Shift AsInteger, ByVal X As Single, ByVal Y As Single)'第三个按钮的背景色为蓝色,其余两个为灰色CommandButton3.BackColor = &HFF8080CommandButton1.BackColor = &HC0C0C0CommandButton2.BackColor = &HC0C0C0End Sub以上三个过程分别为三个按钮的鼠标移过事件,当鼠标移过某按钮时,将当前按钮的背景色设置蓝色,其它两个按钮的颜色设置为灰色。(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式,使工作表中的控件处于可用状态;Excel VBA 程序开发自学通2020-2-26第 420页 /共 508页(7)将鼠标指针指向第二个按钮,那么第二个按钮将呈然蓝色显示,其它两个按钮呈灰色显示,见图 12.25 所示。如果鼠标移向第三个按钮,则第三个按钮蓝色显示,其它两个按钮灰色显示。图 12.25鼠标移过时突出当前按钮本例文件参见光盘:..\ 第十二章\切换按钮背景色.xlsm12.4.2鼠标移动录入姓名〖案例要求〗:借用组合框的下拉菜单在单元格录入姓名,但是不需要鼠标单击目标,而是鼠标移动时将姓名录入到指定的单元格。〖实现步骤〗:(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【组合框(ActiveX 控件)】,并在工作表中按下左键拖动,绘出一个组合框;(2)在 F1:F10 建立辅助区,存放 10 个姓名;(3)右键单击组合框,从菜单中选择【属性】,将其“ListFillRange”属性设置为 F1:F10,表示组合框中引用 F1:F10 的值;(4)再右键单击组合框,选择菜单【查看代码】,进入工作表事件代码窗口;(5)删除默认产生的代码,并输入以下代码:'声明事件过程,在鼠标移过组合框时触发Private Sub ComboBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer,ByVal X As Single, ByVal Y As Single)On Error Resume Next'让组合框自动调整大小ComboBox1.AutoSize = True'将组合框中鼠标箭头下的姓名赋与 A1[A1] = [F1:F10].Cells(Y \ ComboBox1.Font.Size + ComboBox1.TopIndex + 1)End Sub(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而激活组合框;(7)单击组合框,鼠标移过下拉列表中任意项目,该项目将出现在 A1 单元格中,见图 12.26 所示:Excel VBA 程序开发自学通2020-2-26图 12.26第 421页 /共 508页鼠标移过组合框输入姓名到 A1本例文件参见光盘:..\ 第十二章\鼠标移动录入姓名.xlsm12.4.3鼠标移过组合框时加载图片〖案例要求〗:借用组合框的下拉菜单在单元格显示图片。当鼠标移过组合框时,调用当前工作簿同路下 PIC 文件夹中的同名图片。图片及图片名如图 12.27 所示:图 12.27当前工作簿同目录下 PIC 文件夹中的图片〖实现步骤〗:(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【组合框(ActiveX 控件)】,并在工作表中按下左键拖动,绘出一个组合框;(2)再单击功能【开发工具】\【插入】\【图像(ActiveX 控件)】,并在工作表中按下左键拖动,绘出一个图像控件;(3)将图像控件拖到 A2 单元格,将单元格的高度和宽度调至图像控件大小;(4)在 A3:A22 建立辅助区,存放 20 个图片的文件名;(5)右键单击组合框,从菜单中选择【属性】,将其“ListFillRange”属性设置为 A3:A22,表示组合框中引用 A3:A22 的值;(6)再右键单击组合框,选择菜单【查看代码】,进入工作表事件代码窗口;(7)删除默认产生的代码,并输入以下代码:'声明事件过程,在鼠标移过组合框时触发Private Sub ComboBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer,ByVal X As Single, ByVal Y As Single)On Error Resume NextExcel VBA 程序开发自学通2020-2-26第 422页 /共 508页'让组合框自动调整大小ComboBox1.AutoSize = TrueImage1.AutoSize = True'将组合框中鼠标箭头下的姓名赋与 A1Image1.Picture = LoadPicture(ThisWorkbook.Path & "\PIC\" & [A3:A22].Cells(Y \ComboBox1.Font.Size + ComboBox1.TopIndex + 1) & ".jpg")End Sub以上代码中“Image1.AutoSize = True”表示让图象控件随加载图片的大小而变化。所以当文件夹下图片大小不同时,图象控件会相应的变化。(8)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式;(9)单击控件,鼠标箭头从组合框的下拉列表中移动时,图像控件中会根据鼠标箭头下的图片名而调用不同的图片。图 12.28鼠标移过组合框加载图片到图像控件根据需要,也可以将辅助区的文件名存放到其它区域再隐藏起来,从而不影响当前表其它数据;还可以将它存到其它工作表中,VBA 仍然根据列框中的文件名调用当前工作簿路下的 PIC 文件夹中的同名图片。LoadPicture 用于对图像控件加载图片,它的完整浯法如下:object.Picture = LoadPicture( pathname )其中参数 pathname 是一个图片文件的完整路径。本例文件参见光盘:..\ 第十二章\鼠标移过加载图片.xlsm12.4.4鼠标移过列表框时输入品名与单价〖案例要求〗:当鼠标移过列表框时,将鼠标箭头下的产品与单价同时引入 B 列最后一次选择的单元格。〖实现步骤〗:(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【列表框(ActiveX 控件)】,并在工作表中按下左键拖动,绘出一个列表框;(2)在 E1:F11 建立辅助区,存放产品的品名与单价。也可以将辅助区建立在其它工作表;(3)右键单击列表框,从菜单中选择【属性】,将其“ListFillRange”属性设置为 E2:E11,表示列表框中引用 E2:E11 的值;Excel VBA 程序开发自学通2020-2-26第 423页 /共 508页(4)再右键单击列表框,选择菜单【查看代码】,进入工作表事件代码窗口;(5)删除默认产生的代码,并输入以下代码:'声明工作表事件过程,在选择单元格时触发事件Private Sub Worksheet_SelectionChange(ByVal Target As Range)With ListBox1'只有选择第二列时出现列表框IF Target.Column = 2 Then.Visible = True '显示列表框'设定列表框的边距、高度和颜色,列表框显示在右下方一个单元格.Left = Target(1).Offset(0, 1).Left.Top = Target(1).Offset(1, 1).Top.Height = ListBox1.ListCount * ListBox1.Font.Size + 4.BackColor = &HFFC0C0Else'否则隐藏.Visible = FalseEnd IFEnd WithEnd Sub'声明工作表事件过程,鼠标移过列表框时触发Private Sub ListBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByValX As Single, ByVal Y As Single)'将品名和单价同时输入最后一次选择的单元格ActiveWindow.RangeSelection(1) = [E2:E11].Cells(Y \ ListBox1.Font.Size +ListBox1.TopIndex + 1) _& "(单价" & [F2:F11].Cells(Y \ ListBox1.Font.Size + ListBox1.TopIndex + 1) & ")"End Sub(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式;(7)在 B 列以外的任意单元格单击,列表框自动隐藏;(8)选择 B 列任意单元格,在活动单元格的右边一列、下边一行将出现列表框。当鼠标箭头移过列表框中任意品名时,在单元格中将同时产生品名与该产品的单价,见图 12.29 所示:图 12.29鼠标移过时输入品名与单价RangeSelection 属性返回工作表中最后一次选定的单元格。而 Selection 表示当前选择的对象。例如选择 A1 单元格再单击图表,那么此时 RangeSelection 代表 A1,而Selection 代表图表。本例文件参见光盘:..\ 第十二章\鼠标移过列表框输入品名与单价.xlsmExcel VBA 程序开发自学通2020-2-26第 424页 /共 508页登堂篇:VBA 数组、窗体与控件第十三章 数组基础数组在是 VBA 中很重的要概念。善用数组可以简化代码,也可以提升 VBA 程序的执行效率。学习数组之后,读者可能会发现前面 12 章的很多代码都可以用数组来重写,从而大大地提升执行效率,事实上正是如此。本章要点: 数组基础 内置数组函数Excel VBA 程序开发自学通2020-2-26第 425页 /共 508页13.1 数组基础数组主要用于批量地管理数据,或者为了代码提速度,将区域对象转换成内存数组,从而减少读者时间。在 VBA 中,善用数组可以大大地对程序提速,甚至完成某些其它方式无法完成的工作。13.1.1数组概念数组就是一个列表或一组数据表。它是连续可索引的具有相同内在数据类型的元素所组成的集合,数组中的每一元素具有唯一索引号。更改其中一个元素并不会影响其它元素。例如“{1,2,3}”就是一个数组,它的每个成员拥有相同的数据类型,利用索引号可以获取数组中的每个成员。该索引号总是唯一的。数组存于内存中,可以利用索引号获取该集合中每一个子集。鉴于这个特性,它引申出另外两个知识点: 读写速度快VBA 读取对象中的值永远慢于读取内存中的值,所以只有一有机会,VBA 爱好者总会袋借用安适组来对程序提速。 无法永远保存数据存入工作表区域,可以永久保存。但存入内存中的变量数组和常量数组却受其作用域影响生命周期。过程级别的私有变量数组变量或者常量数组在过程结束后会自动释放,结束其生命周期;而公有的变量数组常量数组在 Excel 应用程序关闭后会自动释放。也就是在重启 Excel 后,以前的任何数组都不再存在。数组可以依据两个标准来分类。按数组元素是否固定可以分为静态数组和动态数组;按其维数可以分为一维数组和二维数组、三维数数……在本节对所有数组的相关知识都会进行详细叙述及演示。13.1.2数据的维数数组可以是一维、二维……直到六十维。然而工作中通常使用一维数组和二维数组,因为 Excel 程序的每一行或者每一列就可以转换成一维数组,而多行多列的区域可以转换成二维数组,日常工作正是以行、列或者区域中为裁体来处理数据,所以本书中也以一维和二维数组为中心演示数组的功能与实际应用。1.一维数组数组的值可以取自于单元格区域,将区域直接赋值到数组。那么借用区域来理解数组也就成了最直观的一种方式。例如图 13.1 中 A1:F1 的是一行横向区域,用工作表中的数组公式可表示为“={1,2,3,4,5,6}”;而图 13.2 中 B1:B6 是一列纵向向区域,用工作表中的数组公式可表示为“={1;2;3;4;5;6}”。Excel VBA 程序开发自学通2020-2-26图 13.1 一维横向数组公式第 426页 /共 508页图 13.2 一维纵向数组公式VBA 中的数组也可以像上图中的工作表数组公式一样表示横向和纵向的一维数组。例如:[{1,2,3,5,6}]——表示一维横向数组验证它是否一维横向数组的最好办法是将它赋值给一相同大小的单元格区域。代码如下:Sub 赋值()[a1:f1] = [{1,2,3,4,5,6}]End Sub当执行以上过程后,可以得到图 13.1 的结果,那么[{1,2,3,4,5,6}]是一维横向数组。[{1;2;3;4;5;6}]——表示一维纵向数组同样可以借用区域赋值的方式验证:Sub 赋值()[b1:b6] = [{1;2;3;4;5;6}]End Sub以上过程可以获得与图 13.2 所示同样的效果。同时,根据以上两个赋值的过程,我们可以看到数组在 VBA 中的优势,即不需要对单元格循环赋值,可以将原本需要循环 6 次的操作集中在一次完成赋值。2.二维数组图 13.3 中 B1:E4 是四行四列的区域,利用工作表数组公式对这个区域赋值可以使用以下公式:={1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}图 13.3 二维数组在 VBA 中也可以利用数组表示是一个二维数组,例如:[{1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}]——四行四列的二维数组执行以下过程可以完成图 13.3 所示效果:Sub 赋值()[b1:e4] = [{1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}]End SubExcel VBA 程序开发自学通13.1.32020-2-26第 427页 /共 508页利用索引号获取数组中的元素类似于 Range 可以使用索引号访问区域中的每一个单元格一样,一维数组和二维数组也可以利用索引号获取数组中的每一个值。它主要有两种形式:Arr(Item)Arr(RowIndex,ColumnIndex)这两种方式在形式上看与 Range 的索引号完全一致,然而事实上却存在很多差异。在 Range 中,不管是一维区域还是二维区域都可以利用以上任何方式访问区域中的单元格,例如:MsgBox Range("A1:A20")(7).Address(0, 0)MsgBox Range("A1:A20")(3, 1).Address(0, 0)MsgBox Range("A1:D20")(7).Address(0, 0)MsgBox Range("A1:A20")(4, 2).Address(0, 0)以上四种方式访问区域中某个单元格地址都正确。但数组中却不能混用。当数组是一维时,只能利用第一种方式访问数组中的元素,而数组是二维时,则只能利用第二种方式访问其中每个元素。例如以下的过程中,对一维数组的两种索引方式只能前者可以正常执行,后者会产生错误:Sub 利有用索引号引用数组中的元素()Dim arr1() '声明数组变量arr1 = Array("甲", "乙", "丙", "丁") '对数组赋值MsgBox arr1(1)'正确的引用MsgBox arr1(1, 1) '错误的引用End Sub对于二维数组,以下两种索引方式也只能第一种方式正确,后者会产生运行时错误:Sub 利有用索引号引用数组中的元素()Dim arr1() '声明数组变量arr1 = [{1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}] '对数组变量赋值MsgBox arr1(3, 2) '取值,正确的引用方式MsgBox arr1(7)'取值,错误的引用方式End Sub当使用索引号引用数组中的无素时,有一个很重的设置需要注意——第一个元素的默认索引值。在默认状态下,如果模块中未指定第一个元素的索引号,那么默认为 0。即数组arr 中的第一个值用 arr(0)表示,最后一个元素的索引号则为数组元素个数减 1 来表示。所以下面的过程中,如果未指定第一个元素的默认索引值,那么结果为“乙”,而非“甲”。Sub 利有用索引号引用数组中的元素()Dim arr1() '声明数组变量arr1 = Array("甲", "乙", "丙", "丁") '对数组赋值MsgBox arr1(1)'正确的引用End Sub如果不习惯这种默认的索引方式,VBA 提供了专用的语句改变第一个元素的索引号:Option Base 语句。Option Base 1——表示数组中第一个元素的索引号为 1Option Base 语句只能置于模块的顶部,而且可选值只有 0 和 1。因为默认状态即Excel VBA 程序开发自学通2020-2-26第 428页 /共 508页为 0,那么需要“Option Base 0”时可以忽略。Option Base 语句仅仅作用于当前模块,如果需要修改第一元素默认索引号为 1时,需要所有模块都使用该语句。13.1.4声明数组与赋值数组与其它变量一样,需要声明后再使用,犹其是数组更需要显示声明。1.声明数组变量声明数组和其它变量一样,可以使用 Dim、Static、Private 或 Public 语句来声明。标量变量(非数组)与数组变量的不同在于通常必须指定数组的大小。若指明数组的大小,则它是固定大小数组。若程序运行时数组的大小可以被改变,则它是个动态数组。在声明数组变量时,可以指定数组的第一元素索引号,也可以指定数组的维数。当数组变量的参数是一个数值时,表示它是一维横向数组,元素个数等于该值加1。例如:Dim arr(5)——表示声明一个具有 6 个元素的横向一维数组,其数据类为变体VariantDim arr(4) as byte——表示声明一个具有 5 个元素的横向一维数组,其数据类为Byte如果借用 To 关键字,可以指定数组中第一个元素的索引值。例如:Dim arr(1 To 3) As String——表示声明一个具有 3 个元素的一维横向数组,数据类型为 String,其第一元素的索引号为 1如果需在声明二维数组,可以使用逗号将参数分开,其形式为 arr(一维,二维)。例如:Dim arr(3, 2) As String——表示声明一个 4 行乘 3 列的二维数组,默值第一元素索引值为 0Dim arr(1 To 3,1 To 2) As String——表示声明一个 3 行乘 2 列的二维数组,默认第一元素索引值为 1VBA 允许一个二维数组中不同维的第一元素索引值不同。例如:Dim arr(1 To 4, 2) As String——表示 4 行乘 3 列的二维数组,第一维中第一元素的索引值是 1,而第二维中第一元素的索引值是 02.对数组变量赋值相对于标量变量赋值,对数组变量赋值的方式更复杂,因为数组是多个元素的集合。数组赋值通常采用三种方式:利用循环逐个赋值、利用 Array 对一维数组变量赋值、直接将区域赋与数组。以下过程是对数组逐个赋值,最后输入数组中第三个元素:Sub 数组赋值()Dim arr(3) As String, Item'循环数组的四个元素For Item = 0 To 3'逐个赋值,将 A1:A4 的值赋与每个变量Excel VBA 程序开发自学通2020-2-26第 429页 /共 508页arr(Item) = Cells(Item + 1, 1)NextMsgBox arr(2) '验证数组中的值End Sub以下过程是将一个数组一次性赋值给变量,但是声明变量时必须使用变体型Variant:Sub 数组赋值()Dim arr As Variant '必须用变体型'一次性对数组赋值,横向一维数组arr = Array("甲", "乙", "丙", "丁")MsgBox arr(2) '验证数组中的值End Sub以上方式赋值时,数组 arr 的第一元素默认索引号为 1。也可以对一维纵向数组赋值:Sub 数组赋值()Dim arr As Variant '必须用变体型'一次性对数组赋值arr = WorksheetFunction.Transpose(Array("甲", "乙", "丙", "丁"))[a1:a4] = arr '验证:将数组的值导出到 A1:A4End Sub直接将区域的值赋与变量也是工作中较常见的赋值方式。例如在区域中循环、查找某个字符串,速度远远低于在内存中查找某字符串,此时通常将区域赋与数组,然后在数组中进行查找。下例中即为将区域的值赋与数组Sub 数组赋值()Dim rngrng = ActiveSheet.UsedRange.ValueEnd Sub如果需要从数组变量中取值,可以使用“MsgBox rng(2, 3)”,但不能使用“ MsgBoxrng(2)”这种形式取值。提示:数组的维数一旦在声明时指定,就不能再改变。如果声明是不确定维数组,则需要声明为动态数组。13.1.5静态数组与动态数组静态数组在执行期间不可改变其上界(最后一个元素的索引号),而动态数组可以随时修改其上界。静态变量的声明方式前面已经讲过,声明变量时指定其大小即可。例如:Dim arr(3) As LongDim arr2(4, 1, 1 To 5) As Byte而声明动态的数组时,需要 Dim 语句配合 ReDim 语句或者 ReDim Preserve 语句来实现。ReDim 语句或者 ReDim Preserve 语句的作用是为动态数组变量重新分配存储空间,包括指定维数及声明其上界。但 ReDim 语句重置数组大小时,会使数组中的值丢失;而 ReDim Preserve 语句重置数组的大小时可以保留原数组中的值。可以使用 ReDim 语句反复地改变数组的元素以及维数的数目,但是不能在将一个数组定义为某种数据类型之后,再使用 ReDim 将该数组改为其它数据类型,除非Excel VBA 程序开发自学通2020-2-26第 430页 /共 508页是 Variant 所包含的数组。通过以下代码可以比较它们两者的区别:假设工作表中有图 13.4 所示数据,将区域的值赋与数组后再分别用两种方法重置数组大小,看看它们的变化:Sub 重置数组()Dim arr(), arr2() '声明一个数组arr = [A1:E10].Valuearr2 = [A1:E10].ValueReDim arr(1 To 2, 1 To 3)'重置数组大小为 2 行 3 列的二维数组ReDim Preserve arr2(1 To 10, 1 To 3)'重置数组大小为 2 行 3 列的二维数组MsgBox "arr(2,3):" & arr(2, 3) & Chr(10) & "arr2(2,3):" & arr2(2, 3)End Sub当执行以上过程后,其对果见图 14.5 所示,ReDim 语句重置数组大小后,数组Arr 的值全部丢失,而 ReDim Preserve 语句重置后的数组 Arr2 中的值却保留下来:图 13.4工作表数据图 13.5重置数组大小后13.2 内置数组函数Excel 中有很多内置的用于数组的函数,利用它们可以对数组灵活地进行操作。当然,如果感觉不足时,也可以自己定义数组函数。13.2.1Array:创建一个数组Array 函数用于创建一个包含数组的 Variant。它只能创建一维横向数组。例如:Array("甲", "乙", "丙", "丁")——表示包括四个元素的一维横向数组。可以利用索引号从数组中逐个取出所有值,例如:Array("甲", "乙", "丙", "丁")(1)——表示“乙”,从数组中获取第二个元素的值。Array 方式创建的数组默认状态下下界为 0,随 Option Base 语句的设置而变化。如果需要对 A1 单元格写入“姓名”,对 B1 写入“成绩”,对 C1 写入“名次”,那么借用 Array 可以一次完成,而不需要赋值三次,代码如下:[a1:c1] = Array("姓名", "成绩", "名次")Array 的参数个数可以就是数组的上界,数组上界的大小受计算机的可用内存限制,内存越大,它支持的上界就越大。所以理论上只要内存够大,数组的上界可以无穷大。Excel VBA 程序开发自学通2020-2-26第 431页 /共 508页Array 的参数中各元素的值可以不互相干扰,即它可以是任意数组的数据。例如:Array(123, "ABC", Date, Range("A2"), Format(Now, "e-hh-dd"),12+5)以上数组中包括是文本、数值、日期,单元格对象以及计算表达式,Array 可以有效地将它们转换成一个数组。并可以将该数组的值一次性赋与区域中。提示:Array 只能对 Variant 型变量赋值,且声明该变量时不能包含括号。13.2.2Isarray:判断是否是数组Isarray 函数可以返回 Boolean 值,指出其参数是否为一个数组。例如:IsArray(Array("姓名", "成绩", "名次"))——结果为 TrueIsArray([a1:a2].Value) ——结果为 TrueIsArray(Range("A1").Value)——结果为 FlaseIsArray(Array(1))——结果为 True,虽然只有一个元素,仍然是数组13.2.3Index:从数组中取值Index 是一个工作表函数,非 VBA 函数,但是可以在 VBA 中调用。它的语法如下:Index(arrsy,row_num,column_num)第一参数表示数组;第二参数是表示取数组中行号,下界为 1;第三参数表示数组中列号,下界为 1。Index 从数组中取值时完全不受 Option Base 语句的影响。从以下过程可以比较 Index 与数组索引号取值的区别:Sub 从数组中取值()MsgBox WorksheetFunction.Index(Array(123, "ABC", Date, "你我他"), 1)MsgBox Array(123, "ABC", Date, "你我他")(1)End SubIndex 获取的值为 123,而利用索引号获取的值为“ABC”。也可以从二维数组中获取值,Index 需要指定第三参数:Sub 从数组中取值()aa = [a1:C10].ValueMsgBox WorksheetFunction.Index(aa, 3, 3)End Sub13.2.4Transpose:转置数组Transpose 也是工作表函数,但在数组中常使用它的转置数组,将数组在横向与纵向之间转换。VBA 中声明的一维数组总是横向的,当需要产生一维纵向数组时通常利用Transpose 函数实现转换。例如在 A1:A7 区域产生星期一到星期日的字符串,那么可以使用以下过程:Sub 对纵向区域赋值()Dim arr As Variantarr = Array("星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日")[a1:a7] = WorksheetFunction.Transpose(arr)Excel VBA 程序开发自学通2020-2-26第 432页 /共 508页End SubTranspose 是工作表函数,需要前置 WorksheetFunction 对象。除了本例中一次性赋值的数组外,对其它数组也常常需要用到 Transpose 进行转置。现举一数组查询的实例演示数组的优势和两维数组的定义与转置。要求:工作表中有 A1:A2001 中有 2000 个学生的成绩,现需将不及格的姓名罗列在 D 列,将成绩罗列在 E 列,采用两种方式完成。(1)在 VBE 界面中单击菜单【插入】\【模块】;(2)在模块中输入以下两段代码:Sub 查询不及级人员 1() '非数组Dim arr() As Variant, Item As Integer, rng As RangeDim Tim As LongTim = Timer'遍历所有成绩For Each rng In Range([B2], Cells(Rows.Count, "B").End(xlUp))'如果成绩小于 60If rng < 60 ThenItem = Item + 1 '累加变量'将查找到到了成绩和姓名罗列在 D、E 列Cells(Item, "D") = rng.Offset(0, -1).TextCells(Item, "E") = rng.TextEnd IfNext rng'记录时间MsgBox Format(Timer - Tim, "0.00 秒")End Sub以上过程采用循环、逐个赋值的方式将区域中查询到的目标导出到单元格,在笔者的计算机中查询时间超过 1.5 秒钟。Sub 查询不及级人员 2() '数组方式Dim arr1 As Variant, arr2() As Variant, Item As Integer, MaxRow As Integer, CounterAs IntegerDim Tim As LongTim = Timer'记录最大行数MaxRow = Cells(Rows.Count, "B").End(xlUp).Row'将成绩赋与数组,后续的操作都基于数组arr1 = Range("A2:B" & MaxRow)'遍历数组中所有成绩For Item = 1 To MaxRow - 1'如果成绩小于 60If arr1(Item, 2) < 60 Then'累加计数器Counter = Counter + 1'重新分配数组空间,只能更新末维的大小,所以数组是横向的,两行多列ReDim Preserve arr2(1 To 2, 1 To Counter)'对第二个数组第一行、最末维赋值arr2(1, Counter) = arr1(Item, 1)'对第二个数组第二行、最末维赋值arr2(2, Counter) = arr1(Item, 2)End IfNext'将第二个数组转置后导出到 D 列Excel VBA 程序开发自学通2020-2-26第 433页 /共 508页Cells(1, "D").Resize(Counter, 2) = WorksheetFunction.Transpose(arr2)MsgBox Format(Timer - Tim, "0.00 秒")End Sub以上过程首先将区域转化成数组,然后在数组中进行查询,且将查到的目标数组赋与新的数组。当找到所有目录后一次性将数组的值导出到区域中。该过程在笔者的计算机上执行时间在 0.3 秒钟左右。图 13.6 查找所有不及格人员姓名与成绩罗列在 D、E 列在第二个过程中,使用数组后已经大大地提升查询速度。但是在循环体中使用ReDim Preserve 语句,相当于声明 2000 次数组,它仍然需要消耗时间。本例也可以改为数组的声明方式,避过多次重置数组大小这个问题。代码如下:Sub 查询不及格人员 3()'数组方式Dim arr1 As Variant, arr2() As Variant, Item As Integer, MaxRow As Integer, CounterAs IntegerDim Tim As Long, j As IntegerTim = Timer'记录最大行数MaxRow = Cells(Rows.Count, "B").End(xlUp).Row'记录不及格的人员个数j = WorksheetFunction.CountIf(Range("A2:B" & MaxRow), "<60")'将成绩赋与数组,后续的操作都基于数组arr1 = Range("A2:B" & MaxRow)'重置第二个数组大小,两列多行ReDim arr2(1 To j, 1 To 2)For Item = 1 To MaxRow - 1'如果成绩小于 60If arr1(Item, 2) = 0 Then str = str & arr1(i) & ","Next iMsgBox "共有项:" & Left$(str, Len(str) - 1)End Sub以上过程中,前一段将 Filter 函数的第二参数设置 False 表示将 Arr2 中不包含 Arr1的每一个元素组成一个数组,该数组中每个元素就是 Arr2 有而 Arr1 没有的项目。第二段则借用一个中间变量来记录两个数组共有的项目,将其与逗号串连成一个字符串。Filter 函数在比较两个值是否相同时,采用的近似匹配,所以“广东广州”才后等于“广东”。本例文件参见光盘:..\ 第十三章\两个数组中相同项与不同项.xlsmExcel VBA 程序开发自学通2020-2-26第 437页 /共 508页第十四章 开发数组函数与数组应用善用数组可以对程序大大地提速。本章就数组的应用进行案例讲解,包括开发数组方面的函数和数据查询等应用。通过本章地学习,读者完全可以借用数组对本书前 12 章的程序进行改造,提升其执行速度。本章要点: 自定义数组函数 数组应用案例14.1 自定义数组函数在第 6 章中关于开发自定义函数的讲解时曾有一个数组函数,而本节对数组函数再进行深入地研究。14.1.1定义数组函数要点开发自定义的数组函数和非数组函数相比较,更复杂一些。它值遵循一些规则。首先,在声明函数时一定要声明为变体型 Variant,或者不指定数据类型。其次,按照用户的通常习惯,工作中使用一维纵向数组更多,而 Array 函数只能产生一维横向数组,将 Array 产生的数组赋与函数时需要进行转置。最后,必须要函数的说明中明确指定这是数组函数,否则用户在使用中可能频频出错。Excel VBA 程序开发自学通14.1.22020-2-26第 438页 /共 508页获取工作表目录〖案例要求〗:获取所有工作表名〖过程代码〗:Rem 获取所有工作表名,组成一个数组Function Sheets() As VariantApplication.Volatile'声明变量,包括一个变体型 Arr 变量,将用它来获取所有工作表名,再将其赋与函数Sheets'Sheets 和 Arr 都只能声明为变体型Dim sht As Worksheet, arr As Variant, Item As Integer'重置数组的大小和维数。其上界等于工作表数量'因函数名是 Sheets,避免产生冲突,必须添加前缀 ActiveWorkbookReDim arr(1 To ActiveWorkbook.Sheets.Count)'遍历所有工作表For Each sht In ActiveWorkbook.SheetsItem = Item + 1'将工作表名赋与变量数组arr(Item) = sht.NameNext sht'将数组赋与函数Sheets = WorksheetFunction.Transpose(arr)End Function〖注意事项〗:1.声明函数时必须使用变体型;2.因 Sheets 是 VBA 内置对象名称,在引用工作表对象集合时需要添加对象库;3.数组 Arr 默认是一维横向数组,赋与函数时转置与否都可以。因为工作表中也可以转置。4.为了让工作表新增、删除时公式的结果自动更新,必须使用“Volatile”。〖功能测试〗:返回工作表中选择 B2:B4 并输入以下公式:=sheets()注意是数组公式,必须以【Ctrl+Shift+Enter】三键结束。如果需要获取其中部分工表名,可以借用 Index 函数来实现。例如:=INDEX(sheets(),ROW(A1))图 14.1 Sheets 函数获取工作表本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsmExcel VBA 程序开发自学通14.1.32020-2-26第 439页 /共 508页星期序列〖案例要求〗:获取所有星期序列,包括英文和中文两种样式。〖过程代码〗:Rem 产生星期序列,有一个可选参数Function Week(Optional Opt As Byte = 1)Select Case Opt'当参数为 1 或者忽略时产生中文序列Case 1Week = WorksheetFunction.Transpose(Array("星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"))'当参数是 2 时产生英文序列Case 2Week=WorksheetFunction.Transpose(Array("Monday","Tuesday","Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))Case Else'否则为空白Week = ""End SelectEnd Function〖注意事项〗:1.当有多种选项时,尽量通过 Optional 设置一个默认选项,简化用户的公式输入工作;2.Array 产生的数组是一维横向数组,面要利用 Transpose 进行转置。如果不想用Transpose 那么可以换一种赋值方式:Week = [{"星期一"; "星期二";"星期三"; "星期四";"星期五"; "星期六"; "星期日"}]。〖功能测试〗:在工作表中选择 A1:A7 区域,然后入以下公式:=Week()——产生 7 个中文星期序列=Week(2)——产生 7 个英文星期序列本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsm14.1.4获取区域的唯一值〖案例要求〗:将引用区域去除重复值,将唯一值利用数组方式罗列出来。〖过程代码〗:Function only(rng As Range)Dim cell As Range, onlys As New Collection, Item As Long, arrOn Error Resume Next'如果引用区域不在已用区域中则函数返回空文本If Intersect(rng, rng.Parent.UsedRange) Is Nothing Then only = "": Exit Function'将区域限制在已用区域中,防止用户选择整列或者全选工作表时造成死机For Each cell In Intersect(rng, rng.Parent.UsedRange)If cell "" Then onlys.Add cell.Value, CStr(cell.Text)Next'重置数组大小,其上界等于为重复数的个数ReDim arr(1 To onlys.Count)Excel VBA 程序开发自学通2020-2-26第 440页 /共 508页For Item = 1 To onlys.Count'将不重复值逐个加入数组 arrarr(Item) = onlys(Item)Next'将数组转置后赋与 onlyonly = WorksheetFunction.Transpose(arr)End Function〖注意事项〗:1. 获取唯一值有很多方法,Countif 或者字典法,或者 Collection 对象法,经过笔者的多次测试,利用 Collection 对象获取唯一值速度最快。它的一个特点是对象集合中所有对象不能存在重值,那么将区域中所有元素加入到该对象集合中后再逐个取出来,就可以过滤掉所有重复值。2.如果要对参数所代表的区域进行循环时,通常都需要限制为该区域与工作表已用区域的交集,从而避免用户以整列、或者以“1:65536”做为参数时造成计算机进行假死状态。而获取已用区域时需要的一个技巧是:用“rng.Parent.UsedRange)”获取目标工作表的已用区域。因为用户可能会引用其它工作表的区域做为参数的函数。〖功能测试〗:假设 A1:A10 有图 14.2 所示的数据,选择 B1:B10 可以获取该区域中唯一值:=only(A1:A10)因唯一值仅四个,那么在超过 4 个单元格以上的区域输入公式后,在第五个单元格开始的区域会产生错误值,解决这个问题有两个办法。一是用 ROWS 函数判断数组 only 的元素个数,将超出部分赋值为空。公式如下:=IF(ROW(1:10)>ROWS(only(A1:A10)),"",only(A1:A10))——公式结果见图 14.3 所示。第二个办法是按普通公式方式输入公式,以 Index 函数从数组中逐个取值:=IFERROR(INDEX(only(A$1:A$10),ROW()),"")图 14.2 获取 A1:A10 唯一值图 14.3 消除错误值本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsm14.2 数组应用案例VBA 读写数组的速度远远快于读写单元格或者工作簿对象,所以操作单元格时往往可以转换为对数组的操作,从而对程序提速。本节就查找方面的应用进行 6 个案Excel VBA 程序开发自学通2020-2-26第 441页 /共 508页例演潮头,加深读者对数组的理解。14.2.1将按姓名排列的纵向学员表转置为按班级横向排列〖案例要求〗:将图 14.4 中按姓名纵向排列的学员表转换换横向排列,以三个班级为分类标准从 D 列开始罗列出来。〖过程代码〗:Sub 区域转置()Dim arr1 As Variant, i As Long, MaxRow As Long, a, b, cDim arr() As Variant'记录最后一个非空行MaxRow = Cells(Rows.Count, 1).End(xlUp).Row'将区域赋与数组arr1 = Range("A2:B" & MaxRow)'将三个班级赋与新数组arr = [D1:D3].Value'重置数组大小(保留原来的值):三行 MaxRow 列ReDim Preserve arr(1 To 3, 1 To MaxRow)For i = 1 To MaxRow - 1'将找到的数据Select Case arr1(i, 2)'根据班级将姓名加入到数组相应的行中Case arr(1, 1)a = a + 1: arr(1, a + 1) = arr1(i, 1)Case arr(2, 1)b = b + 1: arr(2, b + 1) = arr1(i, 1)Case arr(3, 1)c = c + 1: arr(3, c + 1) = arr1(i, 1)End SelectNext i'将数组赋与区域,多行的学员表已转置为多列Cells(1, 4).Resize(3, MaxRow) = arrEnd Sub〖注意事项〗:1.A、B 列和 D1:D3 两个区域都有必要转化成数组,然后从数组中读写,从而提升效率;2.MaxRow 的作用是让程序自动计算非空单元格最大行,当数据增减时可以体现兼容性;3. ReDim Preserve 语句重置数组时可以保留其值,本例中不能使用 ReDim。〖功能测试〗:返回工作表,执行过程“区域转置”,可以将 AB 列的学员表转置并存 Cells(1,4).Resize(3, MaxRow)区域。Excel VBA 程序开发自学通2020-2-26第 442页 /共 508页图 14.4 按姓名排列的学员表本例文件参见光盘:..\ 第十四章\转置区域.xlsm14.2.2多表学员资料查询〖案例要求〗:在多工作表中查询学员资料,包括成绩、姓名、地址、工作表及学号。图 14.5三个班级的成绩表〖过程代码〗:Sub 成绩搜索()Dim Tim, arr(), intRows As Long, 姓 名 As String, firstAddress As String, cell AsRange'关闭屏幕更新Application.ScreenUpdating = False'清除上次查询信息Range("A:F").Clear'设定查找目标姓名 = Application.InputBox("您想查找谁的成绩?可以输入一个或者多字", "查找目标", , , , , , 2)Tim = Timer '初始化时间变量'遍历工作表(当前存放结果的工作表即除外)For i = 1 To Sheets.Count - 1'在每个表 A 列中查找Set cell = Sheets(i).Range("A:A").Find(what:=姓名, LookAt:=xlPart)Excel VBA 程序开发自学通2020-2-26第 443页 /共 508页If Not cell Is Nothing ThenfirstAddress = cell.Address(0, 0)DointRows = intRows + 1'累加计数器'重定义数组大小ReDim Preserve arr(1 To 6, 1 To intRows)arr(1, intRows) = Sheets(i).Name'数组第一子项目赋值为查找到的数据所在工作表名arr(2, intRows) = cell.Address(0, 0) '数组第二子项目赋值为查找到的数据的单元格地址arr(3, intRows) = cell.Value'数组第三子项目赋值为查找到的姓名arr(4, intRows) = cell.Offset(0, 1).Text'数组第四子项目赋值为查找到的班级arr(5, intRows) = cell.Offset(0, 2).Text'数组第五子项目赋值为查找到的学号arr(6, intRows) = cell.Offset(0, 3).Text'数组第六子项目赋值为查找到的成绩Set cell = Sheets(i).Range("A:A").FindNext(cell)Loop While Not cell Is Nothing And cell.Address(0, 0) firstAddressEnd IfNext'如果找到有目标If intRows > 0 Then'将找到的值赋与单元格区域,然后添加边框与标题Range("A2:F" & intRows) = Application.Transpose(arr)[A1:F1] = Array("工作表", "地址", "姓名", "班级", "学号", "成绩")Range("A1:F" & intRows).Borders.LineStyle = xlContinuousEnd IfApplication.ScreenUpdating = TrueMsgBox Format(Timer - Tim, "0.00") & "秒" '提示总运行时间End Sub〖注意事项〗:1.因学员资料存在多工作表中,所以需要利用循环查找所有存放学员资料的工作表;2. Find 的参数“LookAt:=xlPart”表示模糊查找,输入“张”可以找到所有姓“张”者的资料;3.查找数据时必须针对未查找数组时的状况进行处理,在本例中借用“intRows >0”来判断中是否有查找到目标。〖功能测试〗:返回工作工作表中后,执行过程“成绩搜索”将弹出一个对话框让用户输入待查询对象的姓名,假设输入“张”,那么查询结果见图 14.6 所示:Excel VBA 程序开发自学通2020-2-26图 14.6第 444页 /共 508页多表资料查询本例文件参见光盘:..\ 第十四章\学员资料查询.xlsm14.2.3自定义百家姓序列〖案例要求〗:生成百家姓序列,使工作表中输入姓氏时可以填充式输入。〖过程代码〗:Sub 序列()'生成百家姓序列Application.ScreenUpdating = FalseOn Error Resume NextCells(Columns.Count, 1).EntireColumn.Clear '清除最后一列Dim Item As Integer, arr(1 To 477)'将百家姓写入常量中,总共 477 个百家性Const 单姓 As String = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康伍余元卜顾孟平黄和穆" & _"萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯昝管卢莫柯房裘缪干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣" & _"翁荀羊于惠甄曲家封芮羿储靳汲邴糜松井段富巫乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭历戎祖武符刘景詹束龙叶幸司韶郜黎蓟溥印宿白怀蒲邰从鄂索咸籍赖卓蔺屠蒙池乔阳郁胥能苍双闻莘党翟谭贡劳逄姬申扶堵" & _"冉宰郦雍却璩桑桂濮牛寿通边扈燕冀浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庾终暨居衡步都耿满弘匡国文寇广禄阙东欧殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相查后荆红游竺" & _"权逮盍益桓公"Const 复姓 As String = "万俟司马上官欧阳夏侯诸葛闻人东方赫连皇甫尉迟公羊澹台公冶宗政濮阳淳于单于太叔申屠公孙仲孙轩辕令狐徐离宇文长孙慕容司徒司空亓官司寇仉督子车颛孙端木巫马公西漆雕乐正壤驷公良拓拔夹谷宰父谷梁晋楚闫法汝鄢涂钦" & _"段干百里东郭南门呼延归海羊舌微生岳帅缑亢况后有琴梁丘左丘东门西门商牟佘佴伯赏南宫"For i = 1 To 407'遍历 407 个单字百家姓arr(i) = Mid(单姓, i, 1)'将百家姓加入数组中NextFor i = 1 To 140 Step 2'遍历 120 个复姓arr(407 + (i + 1) / 2) = Mid(复姓, i, 2)'将复姓追加入数组中Next i'将数组导入自定义序列Excel VBA 程序开发自学通2020-2-26第 445页 /共 508页Application.AddCustomList WorksheetFunction.Transpose(arr)End Sub〖注意事项〗:1.百家姓可以去百度等搜索引擎查找,然后将它转换成字符串做代码调用;2.因每行代码有长度限制,所以对常量赋值时必须将它截断成多行;3.单姓与复姓的截取时标准不同,所以需要两个变量,MID 的第三参数也不同;4.将百宝姓逐个写入单元格中,然后将区域一次性导入序列也是可行的,然而相对于数组在效率上略差。〖功能测试〗:光标位于过程中任意位置,并按下快捷键 F5 执行过程,然后返回工作表中,在A1 输入赵,当 A1 向下填充时,可以产生百家姓序列,总共 477 个。见图 14.7 所示:图 14.7 填充百家姓本例文件参见光盘:..\ 第十四章\生成百家姓序列.xlsm14.2.4查询两列共有项〖案例要求〗:将 AB 列中相同的数据提取出来。〖过程代码〗:Sub 共有项()'声明变量,包括三个数组变量,其中从区域转换成数组的变量虽然只有单列,但却不能通过一维纵向数组的方式访问Dim arr1() As Variant, arr2() As Variant, arr3() As Variant, Item1 As Integer, Item2 AsInteger, SameCount, Counter As Byte'将区域转换成数组arr1 = Range([A2], Cells(Rows.Count, 1).End(xlUp)).Valuearr2 = Range([B2], Cells(Rows.Count, 2).End(xlUp)).Value'计算两列中相同数的个数,减少 ReDim 的次数。如果不计算相数同,那么可以在循环中多次使用 ReDim Preserve 来解决SameCount=Application.Evaluate("=sumproduct(countif("&Range([A2],Cells(Rows.Count, 1).End(xlUp)).Address & "," & Range([B2], Cells(Rows.Count,2).End(xlUp)).Address & "))")ReDim arr3(1 To SameCount)'嵌套循环,比较 A 列的与 B 列有哪些相同,将相同的值加入数组 Arr2For Item1 = 1 To UBound(arr1)For Item2 = 1 To UBound(arr2)'如果相同加入第三个数组,注意第两个数组与第三个数组的参数个数不同Excel VBA 程序开发自学通2020-2-26第 446页 /共 508页If arr1(Item1, 1) = arr2(Item2, 1) Then Counter = Counter + 1: arr3(Counter) =arr1(Item1, 1)Next Item2Next Item1MsgBox "两列中相同项为:" & Chr(10) & Join(arr3, ",")End Sub〖注意事项〗:1.本例中声明了三个数组,Arr1 和 Arr2 通过一列的区域转换成数组,那么在访问其元素时不能当成一维纵向数组处理,必须使用两个参数,例如 Arr1(1,1),而且其默认下界是 1 不是 0。2. 为了减少数组变量的重置次数,本例中使用了 Evaluate 方法计算两列是相同值的个数,做为数组的上界。当数组上界很大时,此方法会具有速度上的优势。〖功能测试〗:光标位于过程中任意位置,并按下快捷键 F5 执行过程,两列同相同项为图 14.9所示:图 14.8 参赛人员列表图 14.9 相同项列表本例文件参见光盘:..\ 第十四章\获取相同项目.xlsm14.2.5获取文件夹下所有文件详细信息〖案例要求〗:将指定文件夹中所有文件的文件名、创建时间、上次访问时间、上次修改时间、文件大小、文件类、路径等信息罗列在工作表中。〖过程代码〗:Sub 批量获取文件信息()'遍历指定路径两级文件夹内文件信息,如果需要更多级可根据需要修改Dim fd As FileDialog, Fso, Fold, File, Folds, Filess, arr(), Item As Long'让用户选择路径Set fd = Application.FileDialog(msoFileDialogFolderPicker)'如果选择了文件夹则记录路径If fd.Show = -1 ThenPathStr = fd.SelectedItems(1)ElseExit SubEnd If'设置标题Excel VBA 程序开发自学通2020-2-26第 447页 /共 508页[a1].Resize(1, 7) = Array("文件名", "创建时间", "上次访问时间", "上次修改时间", "文件大小(b)", "文件类型", "路径")'利用 FSO 技术获取文件与文件夹相关信息Set Fso = CreateObject("scripting.filesystemobject")Set Fold = Fso.getfolder(PathStr)Set Filess = Fold.FilesSet Folds = Fold.subfolders'将每个文件的名称、创建时间、上次访问时间、修改时间、大小、类型、路径等信息加入数组For Each File In FilessItem = Item + 1ReDim Preserve arr(1 To 7, 1 To Item)arr(1, Item) = File.Namearr(2, Item) = File.DateCreatedarr(3, Item) = File.DateLastAccessedarr(4, Item) = File.DateLastModifiedarr(5, Item) = File.Sizearr(6, Item) = File.Typearr(7, Item) = File.PathNext'将数组导出到单元格并对单元格自动调剌列宽[a2].Resize(Item, 7) = WorksheetFunction.Transpose(arr)Range("A:H").Columns.AutoFitEnd Sub〖注意事项〗:1.获取文件的信息有多种方式,本例中采用了 FSO 技术,在本书第 19 章将对 FSO进行详述;2. ReDim Preserve 语句只能改变数组的末维大小,所以重置大小时必须是横向延伸的数组,而在最后将它转置后再导出到工作表中。〖功能测试〗:光标位于过程中任意位置,并按下快捷键 F5 执行过程,在弹出的对话框中选择文件夹,那么该文件夹中的每一个文件的相关信息都会在工作表中罗列出来。本例文件参见光盘:..\ 第十四章\批量获取文件信息.xlsm14.2.7获取当前表所有批注〖案例要求〗:图 14.10 是成绩表,成绩为空或者不及格者有批注。现要求将所有批注及姓名单独提取出现放在新工作表中,便于查看及打印。〖过程代码〗:Sub 获取批注()Dim Nam As Comment, arr(), Item As Long'遍历所有批注For Each Nam In ActiveSheet.CommentsItem = Item + 1'重新分配数组大小ReDim Preserve arr(1 To 2, 1 To Item)'将姓名与批注导入到数组中arr(1, Item) = "姓名:" & Nam.Parent.Offset(0, -1)arr(2, Item) = "批注:" & Nam.TextExcel VBA 程序开发自学通2020-2-26第 448页 /共 508页Next'将数组转置后赋与单元格With Sheets.Add.Name = "获取批注".Cells(1, 1).Resize(Item, 2) = WorksheetFunction.Transpose(arr)End WithEnd Sub〖注意事项〗:1.可以利用遍历 B 列每个单元格的方式,将有批注的单元格对应的姓与与批注提取出来,但循环批注较循环单元格更快;2.本例也可以先计算批注的数量,然后只需要使用 ReDim Preserve 一次,直接设定其上界,然后循环体中可以不再使用 ReDim Preserve 语句。读者可以延着这个思路去练习一次。〖功能测试〗:光标位于过程中任意位置,并按下快捷键 F5 执行过程,提取出来的数据如图 14.11所示:图 14.9 成绩表图 14.10 将所有批注提取出来置于新表中第十五章 认识窗体与控件窗体与控件可以实现与表格的交互,也可以设计精美的界面。善用控件可以使自己的程序更具个性,以及增强 Excel 的功能。本章要点: UserForm 简介 窗体控件一览 设置控件属性 窗体与控件的事件Excel VBA 程序开发自学通2020-2-26第 449页 /共 508页15.1 UserForm 简介UserForm 即用户窗体,通过它可以操作工作簿、工作表、单元格、批注、图形对象等等,也可以仅仅利用窗体设计单独的程序,完全脱离单元格、工作表等数据载体。15.1.1窗体与控件的用途VBA 中的窗体与控件主要用于以下几方面的: 设计登录窗口 制作数据输入界面 数据查询界面 选项设置窗口 制做程序帮助本书将对以上各类用作做案例演示。15.1.2插入窗体与控件的方法插入窗体的方法是:在 VBE 界面中单击菜单【插入】\【用户窗体】,那么当前工程中将出面一个“UserForm1”。但是插入用户窗体需要注意三点:首先,需要确保当前有打开的工作簿。如果不存在活动工作簿那么无法插入用户窗体其次,是当前工程如果受保护,那么必须解除保护才可以插入新的窗体;当打开多个工作簿时,插入的窗体附属于当前选中的工程,它与活动工作簿无关。而插入控件则是在已插入窗体的基础上进行,只有已存在窗体的情况下才可以插入控件。假 设 当 前已存在窗体“UserForm1 ”,那么双击工程资源管理器 中的窗体名“UserForm1”,进入窗体编辑状态,然后单击菜单【视图】\【窗体】可以显示工具箱,在工具箱中有默认的 15 种控件,用户可以通过单击并在窗体中拖动的方式来插入控件。图 15.1 是窗体与工具箱:图 15.1 窗体与包含默认控件的工具箱Excel VBA 程序开发自学通2020-2-26第 450页 /共 508页假设需要在窗体中插入一个文字框控件,那么可以单击工具箱中的“文字框控件”(图标为 ),然后在窗体中拖动产生一个文字框控件。然后可以用左键按住边框拖动的方式改变其大小。15.1.3使用 Excel 5.0 对话框除了 VBE 中插入窗体外,在表中也可以使用窗体与控件。具体步骤为:(1)在工作表标签上单击右键,从菜单中选择【插入】;(2)在弹出的对话框中选择“MS Excel 5.0 对话框”,从而插入一个宏表对话框;(3)在表中默认已经产生一个窗体和两个按钮,可以通过开发工具中的表单控件来丰富窗体的内容。例如图 15.2 中左边是设置时的界面,右边是运行对话框时的界面。图 15.2 Excel 5.0 对话框5.0 对话话框的执行方式有两种:在对话框中的空白区单击右键,从中选择【执行对话框】,另一种方法是利用 VBA 的 Show 方法显示窗体,例如对话框的名字是“窗体”,那么代码如下:Sub 执行对话框()DialogSheets("窗体").ShowEnd Sub本例文件参见光盘:..\ 第十五章\执行对话框.xlsm15.2 窗体控件一览控件位于工具箱中,可以将它加添加到窗体中强化窗体的功能。默认的控件有九个,用户可以对工具箱添加自定义控件。15.2.1标签标签的英文名称是 Label,用代码引用该控件时也使用 Lable。它在工具箱中的图标为 。在窗体中添加标签时,默认名称和默认 Caption 都是“Lable1”,添加第二个时默认名和默认 Caption 都是“Lable2”,可以在代码中以该名称来引用控件。Excel VBA 程序开发自学通2020-2-26第 451页 /共 508页标签用于在窗体中添加说明性的文本,且该文本在窗体执行阶段是不可修改的,通常由开发者指定标签内容,在物殊情况下也可以根据运行的条件自动选择显示的文本内容。15.2.2文字框文字框的英文名称是 TextBox,它在工具箱中的图标为 。在窗体中添加文字框时,默认名称和默认 Caption 都是“TextBox1”,添加第二个时默认名和默认 Caption 都是“TextBox2”,用户可以随时修改其名称和 Caption 属性。文字框的用途是运行窗体时让用户输入文字或者数值。15.2.3命令按钮命令按钮的英文名称是 CommandButton,它在工具箱中的图标为 。命令按钮的用途是用户单击时可以执行一个或者多个任务。它的显示字符 Caption在窗体执行阶断不可以修改。15.2.4复合框复合框(也称组合框)的英文名称是 ComboBox,它在工具箱中的图标为 。复合框可以画出列表框与文本框的组合。用户可以从列表中选出一个项目或是在一个文框中输入值。15.2.5列表框列表框的英文名称是 ListBox,它在工具箱中的图标为 。列表框用来显示用户可以选择的项目列表。如果不能一次显示全部项目的话,可以滚动其滚动条来显示其它项目,也可以通过代码让列框改变高度来适应列表项目的数量。15.2.6复选框复选框的英文名称是 CheckBox,它在工具箱中的图标为 。复选框用于创建一个方框,让用户容易地选择以指示出某些事物是真或假。15.2.7单选框单选框的英文名称是 OptionButton,它在工具箱中的图标为 。单选框用于显示多重选择,但用户只能从中选择一个项目。Excel VBA 程序开发自学通15.2.82020-2-26第 452页 /共 508页分组框分组框(也称框架)的英文名称是 Frame,它在工具箱中的图标为。分组框用于创建一个图形或控件的功能组,将窗体中的其它控件分组,特别是有单选框时,分组框有助于用于创建多个单选项。15.2.9切换按钮切换按钮的英文名称是 ToggleButton,它在工具箱中的图标为 。切换按钮用于创建一个切换开关的按钮,可以在按下和突起时分别执行不同过程。15.2.10多页控件多页控件的英文名称是 MultiPage,它在工具箱中的图标为 。多页控件类似于分组框,可以将内有某种联系的控件单独做为一组显示。区别是多个分组框可以同时显示,而多页控件一次只能显示一页。它的功能与 TabStrip 控件相近。15.2.11滚动条滚动条的英文名称是 ScrollBar,它在工具箱中的图标为 。滚动条提供在长列表项目或大量信息中快速浏览的图形工具,以比例方式指示出当前位置,或是做为一个输入设备,成为速度或者数量的指示器。通常用它替代数字输入。它的功能与旋转按钮相近。旋转按钮的图标为 。15.2.12图像图像的英文名称是 Image,它在工具箱中的图标为 。图像控件用于在窗体上显示位图、图标,不能显示动画。通常用它做装饰,可以设置背景。15.2.13RefEditRefEdit 也称单元格选择器,它在工具箱中的图标为 。RefEdit 可选择用户选择单元格,并返回该单元格对象。它与 Application.Inputnox最后一参数为 8 时功能相同。Excel VBA 程序开发自学通15.2.142020-2-26第 453页 /共 508页附件控件除默认控件外,用户还可以调用附加控件以强化窗体的功能。事实上很多有用的控件都没有在工具箱中罗列出来,需要用户手工调用。添加附件控件的步骤如下:(1)在显显示窗体的前提下,单击菜单【视图】\【工具箱】;(2)在工具箱右上角空白区单击右键,并选择【新建页】菜单;(3)在空白区单击右键,选择【附加控件】,在“附件控件”对话框中将需要的控件打勾,然后单击“确定”按钮,工具箱的“新页”中将产生新加的控件。图 15.3中即包括 Flash 动画控件、多媒体控件和网页控件三个附加控件。图 15.3 在新页中添加附件控件15.3 设置控件属性默认状态的控件不利于用户使用,所有控件都需要对其部分或者所有属性进行设置,才能使窗体更美观,同时也让用户更易掌握各控件的作用。15.3.1调窗体控件位置与大小控件插入窗体中后,根据需要会对其大小与位置进调整。调整大小时可以选择该控件,并用右键按住其四周的九个控件点之一向任意方向拖动,直到合适大小为止。而对按钮和标签这类控件还可以通过菜单【格式】\【正好容纳】来使大小刚好与显示的文字宽度与高度一致,类似于批注框的 AutoSize 属性。调整位置也和调整大小一样,可以分手工拖动和菜单工具两种方式。手工调整位置即选择对象后随意拖动,甚至可以拖到窗体边缘直到完全不看到控件。而菜单方调整方式没有手工调整的任意性,却可以使控件按一定的方式对齐。例如菜单【格式】中产子菜单【水平间距】、【垂直间距】、【窗体内居中】、【排列按钮】等等,读者可以逐个测试其对齐效果。15.3.2设置控件的顺序当大个控件重叠时,可以调置其顺序。例如窗体中有一个按钮和一个图象控件,如果选插入按钮,后插入图像控件,那么图像控件会覆盖按钮。如果需要将按钮移至Excel VBA 程序开发自学通2020-2-26第 454页 /共 508页图像控件之上,可以采用以下步骤:(1)选择图像控件;(2)单击菜单【格式】\【顺序】\移至底层。当有超过两个控件重叠时,也可以对某个控件进行“上移一层”或者“下移一层”,菜单中有相应的功能按钮。15.3.3共同属性与非共同属性当窗体中有多个控件时,它们总有部分共同属性。对于共同属性部分,可以一次性设置完成,例如窗体中有一个标签和一个按钮控件,那么背景色就是它们的共同属性。可以同时选择两个控件然后单击快捷键【F4】显示“属性”对话框,在对话框中将 BackColor 属性设置为绿色,那么选中的所有控件都会同时具有该属性,见图 15.4所示。图 15.4 设置共同属性对于非共同属性,只能逐个设置。例如按钮控件的 Cancel 属性。15.3.4设置颜色属性很多控件都有颜色属性,包括背景色和字体色,而这两种属性的设置一致。现以设置 ForeColor(字体颜色)为例演示属性设围起过程:(1)选择窗体中的按钮;(2)按下快捷键【F4】显示“属性”对话框;(3)在“ForeColor”右边的方框单击,调出颜色设置选项,默认选项卡为“系统”;(4)切换到“调色板”选项卡,在其中罗列了 48 种颜色,见图 15.5 所示。单击红色方块,按钮的字体立即显示为红色,见图 15.6 所示:Excel VBA 程序开发自学通2020-2-26图 15.5 通过调色板设置字体色第 455页 /共 508页图 15.6 修改字体色之后的按钮如果需要在代码中指定颜色,而非手工设置,最后的办法是手工设置一次,然后将产生的颜色代码复制到代码中。例如本例中红色的代码为“&H000000FF&”,那么完整代码如下:CommandButton1.BackColor = &HFF&————“&H000000FF&”输入到 VBE中后会自动变成“&HFF&”,但功能一致,不会产生副作用。注意在输入代码时不要用引号.15.3.5设置宽与高属性所有控件都有高(Height)和宽(Width)属性。虽然高与宽属性可以利用鼠标拖动调整,但部分时候需要更精确,利用数字才是上策。设置高与宽属性相较其颜色属性更简单,直接在方框中输入数字即可,例如 100或者 25,当回车后可以立即生效。而用代码来设置属性则可以使用以下语句:CommandButton1.Height = 12015.3.6设置 Pictrue 属性命令按钮、复选框、切换按钮、框架、多页控件、图像控件都可以设置背景图片。以命令按钮为例,设置背景图片步骤如下:(1)选择命令按钮,使用快捷键【F4】打开属性对话框;(2)Pictrue 属性默认显示“(None)”,单击“(None)”会出现一个浏览按钮;(3)单击该按钮,弹出“加载图片”对话框,从目录中选择 JPG 或者 BMP 图片后返回窗体界面,按钮的背景将是显示为该图片。当图片的形状与按钮不同时,自动综放适应按钮:15.3.7设置光标属性光标属性指鼠标移过控件时是显示的鼠标形状。以两个按钮分别设置不同光标为Excel VBA 程序开发自学通2020-2-26第 456页 /共 508页例演示:(1)在窗体中插入一个按钮,然后将其 Caption 属性设置为“确定”;(2)按住 Ctrl 键的同时,用鼠标左键拖动按钮,从而实现复制一个按钮,并将其 Caption 属性修改为“取消”;(3)选择“确定”按钮,单击“MouseIcon”属性右边的方框,从弹出的“加载图片”对话框中选择文件夹“C:\WINDOWS\Cursors”(假设用户使用的 XP,系统盘为 C),然后选择光标文件“hnodrop.cur”,也可以上网上载动画光标;( 4 ) 选 择 “ MousePointer ” 属 性 设 置 框 , 从 下 拉 列 表 中 选 择“99-fmMousePointerCustom”;(5)选择“取消”按钮,再选择“MousePointer”属性设置框,从下拉列表中选择“15-fmMousePointerSizeAll”;(6)选择窗体的前提下使用【F5】运行窗体,将鼠标移向“确定”按钮时将显示图 15.8 所示光标形状,而鼠标移过“取消”按钮时则显示为图 15.9 所示光标。图 15.7 设置按钮的 Pictrue 属性图 15.8 手形光标图 15.9 十字光标本例文件参见光盘:..\ 第十五章\设置光标属性.xlsm15.3.8设置复合框复合框与列表框相比其它控件在设置上更复杂,需要设置的项目也更多。现在通过复合框引用工作表中 A1:B5 的值为例,演示复合框的列表属性和样式等等属性。(1)在窗体中插入一个复合框;(2)按下【F4】显示属性对话框,选择 RowSource 属性设置框,并输入“A1:B5”,表示复保框的数据源为该区域的值;(3)选择 BoundColumn 属性设置框,输入“2”,表示默认显示 2 列;(4)选择 ColumnWidths 属性设置框,输入“40,40”,表示每一列的宽度都是 40;(5)选择 ColumnHeads 属性设置框,从下拉列表中选择“True”,表示显示表头;(6)选择 ListStyle 属性设置框,从下拉列表中选择“1- fmListStyleOption”,表示列表样式;(7)单击窗体的空白区,按下【F5】运行窗体,再单击复合框的下拉箭头,那么复合框中会显示 A1:B5 的值,且自动产生列标题,见图 15.10 所示:Excel VBA 程序开发自学通2020-2-26第 457页 /共 508页图 15.10 利用复合框引用工作表中的数据本例文件参见光盘:..\ 第十五章\设置复合框属性.xlsm15.3.9设置 Flash 动画Excel 集成了 Flash 控件,可以嵌入并播放 Flash 动画。但默认状态下该控件未出现在工具箱中,需要用户附件控件才可以调用。现演示附加 Flash 控件并设置其属性之步骤:(1)在工具箱的右下角空白区单击右键,选择菜单【附件控件】;(2)单击列表中的任意控件,然后按下“s”,再按下“h”键,光标将定位于“shockwave flash object”处,单击将该项目打勾。如果不存在该项目,那么请到网上下载 Flash 控件(.ocx 格式);(3)返回工具箱后在工具箱中将多出一个名为“shockwaveflash”的控件,图标为 。单击该按钮并在窗体上拖放产生一个 Flash 控件;(4)选择该 Flash 控件,然后进入属性对话框中,选择“Movie”属性设置框,并输入 Flash 动画的完整路径,例如“E:\第十五章\andy.swf”;(5)选择“EmbedMovie”属性设置框,从下拉列表中选择“True”,表示嵌入Flash 到文件;(6)手工拖动 Flash,调整其大小和边距,然后单击【F5】执行窗体,在窗体中已经显示该 Flash 动画,见图 15.11 所示:图 15.11在窗体中播放 Flash 动画本例文件参见光盘:..\ 第十五章\播放 Flash 动画.xlsm15.4 窗体与控件的事件窗体与控件与工作表、工作簿一样,全都拥有自己的事件。通过事件可以让程序Excel VBA 程序开发自学通2020-2-26第 458页 /共 508页在某些条件下自动执行。15.4.1窗体事件介绍窗体总共有 20 个事件,罗列如下:表 15-1事件AddControlAfterUpdateBeforeDragOverBeforeDropOrPasteBeforeUpdateChangeClickDblClickDropButtonClickEnter、ExitErrorKeyDown 和 KeyUpKeyPressLayoutMouseDown和MouseUpMouseMoveRemoveControlScrollSpinDown 和SpinUpZoom窗体事件列表触发条件当将控件插入到窗体、框架或多页控件中的一个页面中时在通过用户界面更改了控件中的数据后当拖放操作正在进行时该事件发生当用户即将在一个对象上放置或粘贴数据时控件中的数据被改变之前该事件发生当 Value 属性改变时该事件发生在下列两种情况下,发生该事件:用鼠标单击控件;用户最终在几种可能的值中为控件选择一个值。当用户指向一个对象并双击鼠标时每当下拉列表出现或消失时一个控件从同一窗体的另一个控件实际接收到焦点之前,Enter发生。同一窗体中的一个控件即将把焦点转移到另一个控件之前,Exit发生。当控件检测到一个错误,并且不能将该错误信息返回调用程序时按下和释放某键时这两个事件依次发生。按下键时发生KeyDown,而释放键时发生 KeyUp当用户按下一个ANSI键时当一个窗体、框架或多页改变大小时用户单击鼠标按键时发生。用户按下鼠标按键时发生MouseDown;用户释放鼠标按键时发生MouseUp用户移动鼠标时当从容器中删除一个控件时重新定位滚动块时用户单击数值调节钮的向下或向左键时发生SpinDown。用户单击数值调节钮的向上或向向右键时发生SpinUp。当 Zoom 属性的值改变时其它很多控件的大部分事件都与窗体的事件一致。15.4.2显示窗体时随机加载背景图〖案例要求〗:显示窗体时随机加载同路径下“背景”文件夹下的图片。〖过程代码〗:Private Sub UserForm_Activate()Me.Picture = LoadPicture(ThisWorkbook.Path & "\背景\" & Int(Rnd() * 10 + 1) & ".jpg")End Sub插入窗体后,双击窗体进入窗体事件代码窗口,因窗体的默认事件是单击事件,那么它会自动产生“UserForm_Click”的事件代码外壳。删除该代码后录入以上“UserForm_Activate”事件过程的代码。该代码用于加载窗片,当激活窗体时执行该事件。而下面的代码用于显示窗体,代码需要保存在模块中:Excel VBA 程序开发自学通2020-2-26第 459页 /共 508页Sub 显示窗体()UserForm1.ShowEnd Sub〖注意事项〗:1. 只有保存过的工作簿才有 Path 属性,所以使用本代码需要保存工作簿;2.在本工作簿同路径下必须有“背景”文件夹。本例中 10 个图片按 1、2、3……命名。〖功能测试〗:执行过程“显示窗体”,窗体会的自动显示图片背景。多次执行可以随机变化。本例文件参见光盘:..\ 第十五章\随机背景图.xlsm15.4.3初始化窗体时填充列框下拉列表〖案例要求〗:初始化窗体时,将第一个工作表 A 列中所有数据添加到窗体中的复合框。〖过程代码〗:Private Sub UserForm_Initialize()Dim Item As Byte'遍历 Sheets(1)A 列所有非空单元格,逐个添加到复合框For Item = 1 To Sheets(1).Cells(Rows.Count, 1).End(xlUp).RowMe.ComboBox1.AddItem Sheets(1).Cells(Item, 1).TextNext ItemEnd Sub〖注意事项〗:1. 执行本过程前,需要在窗体中插入一个复合框,却该复合框保持默认名称“ComboBox1”,否则引用该对象时会产生错误。如果需要将默认名称修改为其它有意义的名字,代码也相应修改。“UserForm_Initialize”事件在初始化窗体时触发;2. 也可以对 RowSource 赋值,从而实现一次性添加复合框的列表项目,代码如下:ComboBox1.RowSource = Sheets(1).Name & "!" & Intersect(Sheets(1).UsedRange,Sheets(1).[a:a]).Address注意必须有“Sheets(1)”语句,否则它会引用当前工作表的数据。〖功能测试〗:按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示窗体”,当单击窗体中的复合框时,其下拉列表将引用 A1 的值,见图 15.12 所示:图 15.12 窗体初化时设置复合框的数据源Excel VBA 程序开发自学通2020-2-26第 460页 /共 508页本例文件参见光盘:..\ 第十五章\窗体初化时设置复合框的数据源.xlsm15.4.4双击时关闭窗体〖案例要求〗:双击窗体的非标题区关闭窗体。〖过程代码〗:Private Sub UserForm_DblClick(ByVal Cancel As MSForms.ReturnBoolean)Unload MeEnd Sub〖注意事项〗:1. 本事件过程在双击窗体非标题区域时触发。如果在窗体中有其它任何控件,那么双击控件不会触发窗体的“UserForm_DblClick”事件;2. Unload 方法可以关闭指定认的窗体,而“Me”关键字代表当前窗体。〖功能测试〗:按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示窗体”,当双击窗体中标题区以外的任何空白区,窗体会关闭。本例文件参见光盘:..\ 第十五章\窗体初化时设置复合框的数据源.xlsm15.4.5窗体永远显示在上左角〖案例要求〗:让窗体永远显示在屏幕左上角,无法移动其位置。〖过程代码〗:Private Sub UserForm_Terminate()Me.Left = 0Me.Top = 0End SubPrivate Sub UserForm_Layout()Me.Left = 0Me.Top = 0End Sub〖注意事项〗:1.第一个事件是窗体初始化时触发,设置其左边距与上边距皆为 0;2.第二个事件是改变窗体位置时触发,总是强制位左边距与上边距为 0.〖功能测试〗:按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示窗体”,窗体默认显示在屏幕左上角。当按下标题区域拖动窗体移动位置时,松开鼠标后窗体会还原位置。本例文件参见光盘:..\ 第十五章\窗体永远显示在上左角.xlsm15.4.6按比例缩放窗体及滚动窗体〖案例要求〗:将窗体进入放大或者缩小,窗体中的所有控件都都比例相应地缩放。当窗体放大到 100%以上时,可以利用滚动条滚动窗体。〖过程代码〗:Excel VBA 程序开发自学通2020-2-26第 461页 /共 508页在窗体中插入一个标签(Label)、一个旋转按钮(SpinButton)和一个图像控件(Image),并对图像控件设置 Pictrue 属性,以方便观察窗体放大与缩小的变化过程。然后在窗体事件代码窗口输入以下代码:Private Sub UserForm_Initialize() '窗体安能始化时设定转换控件的的最小值、最大值与当前值,以及设置标签控件的显示字符SpinButton1.Min = 10SpinButton1.Max = 400SpinButton1.Value = 100Label1.Caption = "缩放到:" & SpinButton1.Value & "%"End SubPrivate Sub SpinButton1_SpinDown() '缩小旋转按钮的值时将窗体也对应地缩小,且以标签上显示缩小的值Zoom = SpinButton1.ValueLabel1.Caption = "缩放到:" & SpinButton1.Value & "%"End SubPrivate Sub SpinButton1_SpinUp() '缩小旋转按钮的值时将窗体也对应地放大,且以标签上显示放大的值Zoom = SpinButton1.ValueLabel1.Caption = "缩放到:" & SpinButton1.Value & "%"End SubPrivate Sub UserForm_Zoom(Percent As Integer) '缩放窗体时触发,将标签的显示字符设置为与旋转按钮的值保持同步Label1.Caption = "缩放到:" & SpinButton1.Value & "%"If Percent > 99 Then '如果缩放比例大于 99%则显示滚动条ScrollBars = fmScrollBarsBothScrollWidth = Width * Percent / 100ScrollHeight = Height * Percent / 100ElseScrollBars = fmScrollBarsNone '否则不显示滚动条End IfEnd Sub'滚动窗体的动滚动条件时触发,当拉动滚动条时,将窗体的标题修改为滚动方向和滚动的值Private Sub UserForm_Scroll(ByVal ActionX As MSForms.fmScrollAction, ByVal ActionYAs MSForms.fmScrollAction, ByVal RequestDx As Single, ByVal RequestDy As Single,ByVal ActualDx As MSForms.ReturnSingle, ByVal ActualDy As MSForms.ReturnSingle)Me.Caption = "滚动方向:X:" & RequestDx & "Y:" & RequestDyEnd Sub〖注意事项〗:1. 窗体的所有事件中滚动事件和缩放事件较复杂,需要配合其它控件来完成。本例利用旋转按钮来将窗体缩小或者放大,且在标签上显示对应的缩放比例。2.本例中窗体的缩放事件“UserForm_Zoom”根据缩有一天比例来控件滚动条是否显示;3.本例中窗体的滚动事件“UserForm_Scroll”则在用户拖动滚动条时显示滚动的座标。4.从本例中读者也可以看到,在窗体初始化时,可以利用事件设置控件属性,而非通过属性对话框手工设置。〖功能测试〗:1.按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示窗体”;Excel VBA 程序开发自学通2020-2-26第 462页 /共 508页2.默认状态如图 15.13 所示。当按下旋按钮的下箭头时窗体中所有控件都缩小,且在标签上显示比例,见图 15.14 所示:图 15.13 默认状态图 15.14 缩小到 85%3.再按下旋转按钮的上箭头,则将窗体中所有控件放大,放大 114%时效果见图15.15 所示;4.当超过 100%时用户可以拖动滚动条来滚动窗体,使其显示超出窗本的部分,同时在窗本的标题栏显示滚动的座标,见图 15.16 所示:图 15.15 放大到 114%图 15.16 滚动窗体本例文件参见光盘:..\ 第十五章\窗体缩放与滚动事件.xlsm15.4.7控件事件介绍窗体中的任何控件都自己专用事件,但大部分事件窗体的事件在语法上一致。限于篇幅,不再一一罗列各种控件所支持的事件,读者可以在帮助中查询所有控件的事件。例如复选框控件的事件,可以在帮助窗口输入“复选框控件”,然后选择“复选框控件”的帮助,再单击“事件”即可看到它所支持的所有事件。从列表中选择事件名可以查看详细解释与实例。15.4.8在窗体中建立超链接〖案例要求〗:窗体中建立三个网址地链接,鼠标移过时显示下划线及蓝色标示,Excel VBA 程序开发自学通2020-2-26第 463页 /共 508页在窗本的标题显示网址,单击则打开该网址。〖过程代码〗:在窗本中插入三个标签,然后在本事件代码窗口中输入以下代码:'鼠标移过时标签文字显示下划线并蓝色字体显示,窗体标题显示网址Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)Label1.Font.Underline = TrueLabel1.ForeColor = &HFF0000Me.Caption = "http://baidu.com"End Sub'单击时打开对应的网址Private Sub Label1_Click()Shell "explorer.exe ""http://baidu.com"""End SubPrivate Sub Label2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)Label2.Font.Underline = TrueLabel2.ForeColor = &HFF0000Me.Caption = "http://news.163.com"End SubPrivate Sub Label2_Click()Shell "explorer.exe ""http://news.163.com"""End SubPrivate Sub Label3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)Label3.Font.Underline = TrueLabel3.ForeColor = &HFF0000Me.Caption = "http://bbs.excelbbx.cn"End SubPrivate Sub Label3_Click()Shell "explorer.exe ""http://bbs.excelbbx.cn"""End Sub'鼠标移过窗体时还原三个标签的状态Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByValX As Single, ByVal Y As Single)Label1.Font.Underline = FalseLabel1.ForeColor = &H0&Label2.Font.Underline = FalseLabel2.ForeColor = &H0&Label3.Font.Underline = FalseLabel3.ForeColor = &H0&Me.Caption = "请选择网址"End Sub〖注意事项〗:1. 鼠标移过标签后,标签的文字会变化及下划线显示,为了让鼠标离开时可以还原,需要配合窗体的“UserForm_MouseMove”事件来完成;2.单击时打开网址需要对网址用半角双引号引起来,才能防止网址中有空格等特殊字符时不产生错误。在字符串中产生一个双引号“"”的方法是使用两个双引号并排“""”。〖功能测试〗:1.按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显Excel VBA 程序开发自学通2020-2-26第 464页 /共 508页示窗体”;2.当鼠标在“Excel 百宝箱论坛”上移过时,该标签将下划线且蓝色显示,同时在标题栏显示该论坛地址,见图 15.17 所示。如果单击该标签文字,可以打开对应的论坛;3.当移动鼠标到空白区后,标签将原还,且窗体的标题也显示为“请选择网址”。图 15.17 鼠标移过标签时的效果图 15.18 鼠标离开标签时的效果本例文件参见光盘:..\ 第十五章\设计超链接.xlsm15.4.9鼠标移过更新列表框数据〖案例要求〗:工作表中有一班、二班、三班的学员表,现需要在窗体中通过鼠标移过单选按钮的方式显示不同班级的学员资料。〖过程代码〗:在窗体中添加三用单选按钮和一个列表框,然后输入以下代码:'激活窗体时设置三个单选按钮的标题及窗体标题Private Sub UserForm_Activate()Me.OptionButton1.Caption = [A1]Me.OptionButton2.Caption = [B1]Me.OptionButton3.Caption = [C1]Me.Caption = "请选择班级"End Sub'鼠标移过单选按钮时更新列表框数据源Private Sub OptionButton1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer,ByVal X As Single, ByVal Y As Single)Me.ListBox1.RowSource = Range([A1], Cells(Rows.Count, 1).End(xlUp)).AddressEnd SubPrivate Sub OptionButton2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer,ByVal X As Single, ByVal Y As Single)Me.ListBox1.RowSource = Range([B1], Cells(Rows.Count, 2).End(xlUp)).AddressEnd SubPrivate Sub OptionButton3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer,ByVal X As Single, ByVal Y As Single)Me.ListBox1.RowSource = Range([C1], Cells(Rows.Count, 3).End(xlUp)).AddressEnd Sub〖注意事项〗:本例中窗体、单选按钮和列表框等等控件都利用代码来设置其设置,从而侃鼠标移过时改变列表显示效果。虽然可以通过属性对话框来设置,但用代码设置更利于修改与维护。〖功能测试〗:按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示Excel VBA 程序开发自学通2020-2-26第 465页 /共 508页窗体”;鼠标移过单选按钮时,列表框中将显示鼠标指向按钮标题所代表的班级的学员资料。图 15.19 让列表框随鼠标移动显示不同班级的学员资料本例文件参见光盘:..\ 第十五章\让列表框随鼠标移动显示不同班级的学员资料 xlsm15.4.10让输入学号的文字框仅能录入 6 位数字〖案例要求〗:为了规范法学号,要求从窗体中输入学号并导入到单元格。输入时必须是数字,而且必须是 6 位才能导入到单元格。〖过程代码〗:在窗体中插入一个标签,将其“Caption”属性设置为“请输入学号:”,然后添加一个文字框、两个按钮,按钮的“Caption”属性分别设置为“确定”和“关闭”。最后双击窗体并在代码窗口输入以下代码:Dim OldText As StringPrivate Sub TextBox1_Change()'只允许文本框输入数字,CTRL+V 也不行Dim text As String', Item As Integertext = TextBox1'逐一检查是否输入的数字,如果某字符不是数字还原为前面已输入的数字For i = Len(OldText) + 1 To Len(text)If Asc(Mid(text, i, 1)) 57 Then TextBox1 = OldTextNext'记录当前已输入的数字,没有一个数字时则赋空值OldText = TextBox1End SubPrivate Sub CommandButton1_Click()'如果不等于 6 位则退出,否则将数字导出到单元格If Len(TextBox1) 6 ThenExit SubElseActiveCell = TextBox1.textMe.TextBox1 = ""'清空文字框Me.TextBox1.SetFocus'返回文字框等于用户继续输入下一个ActiveCell.Offset(1, 0).Activate'激活下一个单元格End IfEnd SubPrivate Sub CommandButton2_Click()Unload MeEnd Sub〖注意事项〗:Excel VBA 程序开发自学通2020-2-26第 466页 /共 508页1.检查输入的字符是否是数字,通常利用 Asc 判断它的字符码是否在 48 到 57 之间;;2.虽然仅仅在运行按钮的单击事件时也可以检查文字框中是否是 6 位数字,但在输入阶断就进限制可以更有效率;3. SetFocus 使某个控年具有焦点。本例中表示光标定位于文字框。〖功能测试〗:1.按 15.4.2 所示方法在模块中添加一个“显示窗体”的 Sub 过程,然后执行“显示窗体”;2.假设活动单元格是 B2,那么文字框中输簇 578364 并单击回车键将激活“确定”按钮,再次单击回车键,文本框中的学号输入导出 B2 单元格,然后激活 B3、清空文字框,而且光标返回文字框等等用户录入下一个学号,见图 15.20 所示:图 15.20 将文字框的 6 位数字导入到单元格并返回文字框本例文件参见光盘:..\ 第十五章\让文字框仅能录入 6 位数字.xlsm15.4.11鼠标拖动调整文字框大小〖案例要求〗:设计一个带有两个文字框的窗体,用于输入个人简历和求职意向。但需要满足用户可以手动拖动的方式改变文字框宽度,从而适应简历字符的增减、变化。〖过程代码〗:在窗体中插入两个标签,做为文字框的标题;在标签下各插入一个文字框,让用户录入“自我介绍”和“求职意向”;在两个文字框中间插入一个标签,调整其高度为与文字框的高度一致,宽度为 1/3 厘米即可,然后将其 Caption 设为空,该标签用于拖动改变文字框的宽度;最后加添两个按钮,并将其“Caption”属性分别设置为“打印资料”、“关闭窗体”。双击窗体输入以下代码:Private Sub UserForm_Activate() '激活窗体时设置三个标签和属性及窗体的标题属性Label1.Caption = "自我介绍": Label2.Caption = "求职意向"Caption = "应聘书"Label3.MousePointer = fmMousePointerSizeWE '设置第三个标题的鼠标指针为左右箭头Label3.ControlTipText = "按下拖动可调整文字框宽度" '鼠标指向标签可以产生提示End Sub'鼠标移过做为分界线的标签时触发事件Private Sub Label3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal XAs Single, ByVal Y As Single)If Button = 1 Then'如果按下了左键With Label3.BackColor = &HFF& '显示为红色,只有按下拖动时显示'不允许将两个文字框中的任何一个宽度设为 0,,所以预留左、右边距 40If .Left + X >= 40 And .Left + X Me.Width - 40 Or X 0 ThenIf TextBox2 "admin" Then'如果密码不是 adminMsgBox "密码不符,请重新输入!"'提示i=i+1'累加变量,记录错误次数Exit Sub'退出程序Else'否则i=0'恢复计数器为 0Unload Me'关闭窗体Application.Visible = True'显示程序Sheets(1).Activate'进入第一个工作表Application.EnableCancelKey = xlInterrupt '恢复设置End IfElseMsgBox "用户名不符,请重新输入!", 64, "警告"'用户名不对则退出End IfEnd SubPrivate Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)If CloseMode 1 Then Cancel = True'不允许单击关闭窗体的按钮End Sub其中第一段代码用于检证用户是否确入用户名与密码,如果未输入或者输入不是预设的三个用户之一、或者密码不正确,都会产生相应的提示,且等待用户继续输入。如果输入密码和用户名正确,则进入第一个工作表。其中静态变量用于记录用户输入确误的次数,当错误三次后工作簿会自动关闭。Excel VBA 程序开发自学通2020-2-26第 473页 /共 508页第二个过程用于禁用窗体的关闭按钮,防止用户手工关闭验证窗体。(6)为了窗体可以正确启动,还需要在插入的模块中加入以下代码:Sub auto_Open()'开启工作簿执行Application.EnableCancelKey = xlDisabled '禁止中断执行,即禁用快捷键【Ctrl+Break】Application.Visible = False'程序不可见登录.Show'菜单登录框End Sub以上过程表示工作簿启动时自动执行。且禁上用户使用快捷键【Ctrl+Break】来中断验证程序。(7)保存工作簿后重新启动,将弹出图 16.2 所示窗口,此时除输入用户名和密码外无法中断程序,也无法关闭窗口。图 16.2 权限验证窗口本例文件参见光盘:..\ 第十六章\权限验证.xlsm16.1.3设计计划任务向导〖案例要求〗:设计分列或者数据透视图设计一个关于计划任务的向导。即在窗体中设计一个多页控件,每页进行一个方面的设置,最后根据窗体中的设置来完成一个任务。本例中的计划任务是在指定时间注销计算机或者关闭、重启计算机。〖实现步骤〗:(1)插入一个模块,并在模三维中录入以下代码,表示声明三个公共变量:Public 时间类型 As Byte, 任务类型 As Byte, 时间 As String(2)插入一个窗体,并将其“Caption”属性设为“计划任务”;(2)在窗体中插入多页控件,并新建两页,将四页更命为“说明”\“时间”\“任务种类”\“执行”;(3)返回第一页,插入一下标签,录入一些说明性的文字,表示本工具的用途;然后插入一个命令按钮,将其“Caption”属性设置为“开始—>”;(4)双击命令按钮,输入以下代码:Private Sub CommandButton1_Click()Me.MultiPage1.Value = 1 '进入下一页(多页控件的第一页 Value 属性为 0)End Sub(5)在第二页顶部插入标签,对时间选项作说明,详见附件;在下面插入一个分组框,在分组框中插入两个单选钮,将“Caption”属性分别设置为“相对时间”和“绝对时间”;在分组框右插入文字框,用于输入时间;最后插入一个命令按钮,将其“Caption”属性设置为“下一步”;(6)双击“下一步”按钮,输入以下代码:Private Sub CommandButton2_Click()Excel VBA 程序开发自学通2020-2-26第 474页 /共 508页'如果输入了时间值If VBA.IsDate(Me.TextBox1.Text) ThenMe.MultiPage1.Value = 2 '进入下一页时间 = Me.TextBox1.Text '将输入的时间赋与变量End IfEnd Sub'选择不同选项时对变量赋不同的值Private Sub OptionButto1_Click()时间类型 = 1End SubPrivate Sub OptionButton2_Click()时间类型 = 2End Sub第一段代码表示单击命令按钮时对变量赋值,并进入下一页;第二三段代码表示用户选择时间选项的单选按钮时记录选择的项目,如果是绝对时间则值 1,否则为 2;(7)进入第三页,插入说明性的标签,以及三个单选按钮,将其“Caption”属性分别设置为“关机”、“重启”与“注销”;在右边插入一个命令按钮,将“Caption”属性设置为“下一步”;(8)双击“下一步”输入以下代码:Private Sub CommandButton3_Click()Me.MultiPage1.Value = 3 '进入下一页End SubPrivate Sub OptionButton3_Click()任务类型 = 1End SubPrivate Sub OptionButton4_Click()任务类型 = 2End SubPrivate Sub OptionButton5_Click()任务类型 = 3End Sub(9)进入第四页,仍然插入一个说明性的标签,同时插入一个命令按钮,用于启动任务。将按钮的“Caption”属性设置为“启动任务”,然后双击按钮输入以下代码:Private Sub CommandButton4_Click()启动任务 'Call 启动任务Unload Me '关闭窗口End Sub(10)为了对窗体中的选项设置默认值,在窗体事件代码窗口输入以下代码:'窗体激活时执行时Private Sub UserForm_Activate()Me.MultiPage1.Value = 0 '默认显示第一页Me.MultiPage1.Style = fmTabStyleNone '不显示分组框的按钮OptionButton1.Value = True '默认为相对时间OptionButton3.Value = True '默认为关机时间类型 = 1'对变量设默认值任务类型 = 1End Sub多页控件的“Style” 属性设置为“fmTabStyleNone”表示不显示标签,防止用户跳过中间环节而直接进入最后一页。Excel VBA 程序开发自学通2020-2-26第 475页 /共 508页(11)最后到模块中输入以下两段代码,用于根据三个公共变量的值设置计划任务:Sub 启动任务()'如果“时间类型”变量为 1 时则在指定时间启动“任务”过程,否则时间值加上 NowIf 时间类型 = 1 ThenApplication.OnTime TimeValue(时间), "任务"ElseApplication.OnTime Now + TimeValue(时间), "任务"End IfEnd SubSub 任务()'“任务类型”变量为 1 时关机,为 2 时重启,为 3 时注销'其中 Shutdown 命令是 Windows Xp 和 Vista 的 DOS 命令,Win 98 不支持。Select Case 任务类型Case 1Shell "shutdown -s -t 1"Case 2Shell "shutdown -r -t 1"Case 3Shell "shutdown -l -t 1"End SelectEnd Sub“启动任务”过程首先判断变量的值,如果是 1 则 Ontime 的时间参数使用绝对时间,否则在时间值上加上“NOW”成为相对时间;“任务”过程也根据变量的值来决定任务的种类。如果变量为 1 则 shutdown 的参数为“-s”表示关机,变量显 2 时使用能数 R 表示重启,变量显 3 时则使用参数 L,表示注销。其第二参数“-t”表示时间,以秒为单位,本例中设定为 1 秒钟。(12)启动窗体,默认将显示第一页,见图 16.3 所示。单击“开始”按钮可以直入第二页,默认选项为“相对时间”,保持为变,然后在时间框中输入“00:00:05”表示相对于现在 5 秒钟之后执行任划;图 16.3 计划任务第一页图 16.4 计划任务第二页(13)进入第三页后默认选择为“关机”,单击“重启”表示将任务设置为 5 秒钟后重启。单击“下一步”按钮进入最后一页,再单击“启动任务”,在 5 秒钟后电脑后重启。Excel VBA 程序开发自学通2020-2-26图 16.5 计划任务第三页第 476页 /共 508页图 16.6 计划任务第四页本例中仅仅利用窗体与多页控件实现向导,而完全脱离工作表而执行。借用此思路,读者可以开发更多需要向导来执行多步骤的任务,本例文件参见光盘:..\ 第十六章\设计计划任务向导.xlsm16.1.4设动动画帮助〖案例要求〗:在窗体显示滚动文字,为插件提供帮助信息。假设为简繁互换插件设计帮助。〖实现步骤〗:(1)插入一个窗体,将其“Caption”属性修改为“简繁互换插件帮助信息”;(2)利用第 15 章的知识在工具箱中添加一个网页控件,其全名为“Microsoft Web浏览器”;(3)在窗体中插入一个网页控件,且将控件的高度与宽度拖动到与本一致;(4)双击控件打开代码窗口,并录入以下代码:Private Sub UserForm_Activate()Dim str As Stringstr = "简利繁转换工具箱 功能说明: 将选区中简体汉字转换成繁体," _& " 也可以将繁体汉字转换成简体。使用方法:选择待转换成的区域,"_& " 然后单击菜单【简繁转换】在弹出的对话框中输入 1 表示简体转繁体" _& " 在对话框中输入 2 表示将繁体转简体 单击确定后执行转换。" _& " 有建议请发送邮件到: 单击反馈意见"WebBrowser1.Navigate "about:blank"WebBrowser1.Document.writeln ""&str&""End Sub以上过程中“WebBrowser”是 Web 网页控件的名称,“about:blank”表示将网页初化为空白页。下一句中的“Document.writeln”方法则表示向网页中写入代码,相当于利用 VBA 编写网页。代参天中的“bgcolor=#00FF00”表示对网页设置背景色,可以随意修改。“direction=up”动画文字从下向上滚动,“crollamount='4'”表示滚动速度,用户可以根据多方面求调整。Excel VBA 程序开发自学通2020-2-26第 477页 /共 508页网页中的“”代表换行,对变量 Str 赋值时可以在任意地方插入“”。(5)执行窗体后,窗体中将产生绿色背景的网页,网页中的文字会从下向上滚动,见图 16.7 所示。当窗体的文字滚动到最后一行时,会显示“单击反馈信息”,如果单击该字符串将启动 Windows 自带的邮件工具 Outlook Express 程序。图 16.7 动画帮助窗口本例文件参见光盘:..\ 第十六章\设计动画帮助.xlsm16.1.5用窗体浏览图片〖案例要求〗:通过窗体浏览图片,允许用户随意选择图片存放的目录。〖实现步骤〗:(1)插入一个窗体,将其“Caption”属性设置为“图片预览”;(2)在窗体左侧添加一个列表框控件;(3)在窗体右侧添加一个图像控件;(4)双击窗体进入代码窗口,并输入以下代码:'窗体初始化时执行Private Sub UserForm_Initialize()Dim FileName As String, n As Long'设置窗体与列表框的背景色Me.ListBox1.BackColor = &HFFFF80Me.BackColor = 16761024FileName = Dir(PathStr & "*.jpg")'记录 Jpg 图片个数While Len(FileName) > 0n=n+1FileName = Dir()WendIf n > 0 Then '如果文件个数大于 0Image1.Picture = LoadPicture(Dir(PathStr & "*.jpg")) '将第一张图片加载到图像控年FileName = Dir(PathStr & "*.jpg")For I = 1 To n '逐个添加图片名到列表框中ListBox1.AddItem FileNameFileName = DirNext IElse '否则提示不存在并关闭窗体MsgBox "选定的目录下不存在 Jpg 图片。"Excel VBA 程序开发自学通2020-2-26第 478页 /共 508页EndEnd IfEnd SubPrivate Sub ListBox1_Click()'将选择的文件名对应的图片文件加载到图像控件Image1.Picture = LoadPicture(PathStr & ListBox1.Text)End Sub第一个过程首先计算变量 PathStr 所代表的路径下是否存在 JPG 图片,如果没有则关闭窗体;如果有图片则将图片名称逐一添加到窗体中的图像控件。第二个过程用于单击列表框中不同文件名时修改图像控件中的显示图片。其中 ListBox1.Text 表示用户在列表框中选择的项目。代码中的 Dir 用于获取名件名,在本书第 19 会进行详细介绍。(5)为了让用户可以选择图片路径,需要在模块中输入以下代码:Public PathStr As StringSub 图片浏览()Dim fd As FileDialog'让用户选择路径Set fd = Application.FileDialog(msoFileDialogFolderPicker)'如果选择了文件夹则记录路径,否则退出程序If fd.Show = -1 Then PathStr = fd.SelectedItems(1) Else Exit SubPathStr = PathStr & IIf(Right(PathStr, 1) = "\", "", "\")'显示窗体UserForm1.Show 0End Sub以上过程中 PathStr 是工程级的公有变量,必须使用 Public 进行声明,否则窗体中无法调用该变量。(6)保存工作簿,执行过程“图片浏览”,程序将弹出一个“浏览”对话框,当用户选择了存图片的文件夹后(光盘中准备了一个“图片”目录,里面有大量的 JPG图片),会弹出“图片预临”对话框,默认显示用户选择的文件下第一个背片。当用户单击左侧列表框中的任意文件名时,右侧图像控件会显示相应的图片,见图 16.8所示。图 16.8 利用窗体预览图片本例文件参见光盘:..\ 第十六章\利用窗体预览图片.xlsmExcel VBA 程序开发自学通2020-2-26第 479页 /共 508页16.2 窗体与表格的交互前一节中介绍了窗体独脱离工作表的应用,但工作中更多的是窗体与表格实现交互应用。本节通过 5 个案例演示将数据导入窗体以及将窗体数据导入工作表。16.2.1设计多表录入面板〖案例要求〗:当输入值时需要多个工作表中切换时,直接在工作表中输入是很不现实的,效率极低。例如图 16.9 中,需要五个班学员的缴费,且收费时并非按班级统一执行,而是按学员到场的时间先后为序,那么就可能每输入一次切换一次工作表。而利用 VBA 的窗体设计一个输入面板,让程序自动查找工作表则会灵活许多。本例中假设收费标准为 800,在输入学生的缴来学费后,自动将资料导出到相应的班级工作表中。同时查看缴费是否完整,如果其缴费低于 800,则在欠费工作表中一一罗列出来,方便后续的统计。图 16.9收费表分布图〖实现步骤〗:(1)插入一个窗体,将其“Caption”属性修改为“资料输入面板”;(2)在窗体中插入五个标签,并将“Caption”属性分别修改为“班级”、“姓名”、“性别”、“收到学费”与“欠费”;(3)在“班级”右边插入一个复合框,用于显示班级名称;其它四个标签右边备添加一个文字框。特别需要注意的是文字框的顺序,因为录入数据后单击回车键时的跳转由这个顺序决定;(4)最后添加一个命令按钮,将其“Caption”属性设置为“OK”。双击按钮录入以下代码:Private Sub UserForm_Activate()'激活窗体时显示五个班的班名,并设置默认值为一班ComboBox1.List = Array("一班", "二班", "三班", "四班", "五班")ComboBox1.Value = "一班"End Sub以上过程表示激活窗体时将五个班名加入到复合框中,且设置复合框的默认值。Private Sub TextBox3_Change()If Not IsNumeric(TextBox3) Then TextBox3 = "" '输了的非数字则清空If TextBox3.Value > 800 Or TextBox3.Value < 0 ThenMsgBox "请检查后再输入": TextBox3 = 0: TextBox3.SetFocus'不在 0 到 800之间则提示,并反返回 Textbox3ElseExcel VBA 程序开发自学通2020-2-26TextBox4 = 800 - TextBox3.ValueEnd IfEnd Sub第 480页 /共 508页'第四个文字框等于 800 已收到的学费以上代码对输入学费的文字框进行限制,如果非数值则清除等待重新输入;如果不在 0 到 800 范围内则提示,且默认值设为 0,最后将第四个文字框的值设置这为 800减去已缴的学费。Private Sub TextBox3_Exit(ByVal Cancel As MSForms.ReturnBoolean)CommandButton1.SetFocus '将焦点转移到 OK 按钮End Sub以上代码表示文学费文字框中单击回车键时将焦点转移到 OK 按钮,从而跳过欠费的文字框。Private Sub CommandButton1_Click()'单击 OK 时执行'如果未输入时禁用执行If TextBox1 "" And TextBox2 "" And TextBox3 "" ThenWith Sheets(ComboBox1.Value).Cells(Rows.Count, 1).End(xlUp).Offset(1, 0) = TextBox1 '将文字框中的值导入到单元格.Offset(1, 1) = TextBox2.Offset(1, 2) = TextBox3.Offset(1, 3) = TextBox4End WithSheets(ComboBox1.Value).SelectRange("A2:D" & Cells(Rows.Count, 1).End(xlUp).Row).Borders.LineStyle =xlContinuousIf TextBox4.Value > 0 Then Sheets(ComboBox1.Value).Cells(Rows.Count,1).End(xlUp).Resize(1, 4).Copy _Sheets("欠费人员列表").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0) '如果不等于交费 800 则在将数据复制到欠费表中ElseMsgBox "所有文本框不能为空!", vbOKOnly + 64, "提示"End IfTextBox1 = ""TextBox2 = ""TextBox3 = 0TextBox4 = ""ComboBox1.SetFocus '进入复合框,等待选择班级End Sub以上过程在单击“OK”按钮或者在按钮具有焦点时单击回车键而触发。过程中首先检查三个文字框是否空白,若空白拒绝执行,否则将四个文字框的值按顺序导入复合框所指定的工作表中第一个非空行,而且将缴费低于 800 者复制到“欠费人员列表”中。最后清空四个文字框的值,将焦点移至复合框,等待用户处理下一笔资料。(5)保存工作簿后,然后运行窗体,默认状态是显示一班,其它文字框为空。在列表框中可以利用键上的上下箭头来调节班级,当敲下回车后光标定位于姓名输入框。输入姓名后再回车则定位于学费输入框。在输入收到的学费时如果输入非数值会自动清空,如果输入负数或者大于 800 的值时程序会提示用户,等待重新输入,而欠费文字框中则会根据输入的学费自动计算欠费。当输入学费且敲下回车键时,光盘标会定位于“OK”按钮,单击回车键可以将刚才输入的所有数据导入到指定的班级中第一个非空行,见图 16.11。如果欠费大于 0,还会自动将该笔资料复制到“欠费人员列表”工作表。Excel VBA 程序开发自学通图 16.102020-2-26输入姓名、性别与学费第 481页 /共 508页图 16.11输窗体的数据导出到工作表本例中例用窗体输入资料相对于直接在工作表输入资料有四个优势:不用在工作表间切换;不用使用鼠标而全凭键盘操作;不用人工计算欠费;当有欠费时不需要输入两遍,自动复制。本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm16.2.2多条件高级查询〖案例要求〗:工作表中有学生的姓名、成绩与学号,见图 16.12 所示。要求任选其中作为查询条件,但对查询的目标需要罗列出其所有资料,支持模糊查询。〖实现步骤〗:(1)插入一个窗体,将其“Caption”属性设置为“成绩查询”;(2)在窗体中添加一个复合框,在复合框右边添加一个文字框,用于输入查询条件,而下方再添加一个列表框,用于存放查找到的目标数据;(3)双击文字框输入以下代码:Private Sub UserForm_Activate() '激活窗体时设置复合框默认值ComboBox1.List = Array("姓名", "成绩", "学号")ComboBox1.Value = "姓名"End Sub以上过程表示在激活窗体时设置复合框默认值Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)Dim arr(), RowCount As Integer, Item As IntegerItem = 1 '对变量赋值为 1,目的是使数组第一列空白For RowCount = 2 To Cells(Rows.Count, 1).End(xlUp).Row'根据复合框的值决然定查找方式Select Case ComboBox1.ValueCase "姓名"'如果是姓名,则以模糊查找方式对比姓名If Cells(RowCount, 1) Like "*" & TextBox1.Text & "*" ThenItem = Item + 1ReDim Preserve arr(1 To 3, 1 To Item) '重置数组,然后将单元格的值导入数组arr(1, Item) = Cells(RowCount, 1): arr(2, Item) = Cells(RowCount, 2):arr(3, Item) = Cells(RowCount, 3)End IfCase "成绩"If Not IsNumeric(TextBox1) Then Exit Sub'如果复合框中选择成绩,那么文字框禁止输入非数字。If Cells(RowCount, 2).Value * 1 = TextBox1.Value * 1 ThenExcel VBA 程序开发自学通2020-2-26第 482页 /共 508页Item = Item + 1ReDim Preserve arr(1 To 3, 1 To Item)arr(1, Item) = Cells(RowCount, 1): arr(2, Item) = Cells(RowCount, 2):arr(3, Item) = Cells(RowCount, 3)End IfCase "学号"'如果是学号,也按模糊查找方式对比If Cells(RowCount, 3) Like "*" & TextBox1.Text & "*" ThenItem = Item + 1ReDim Preserve arr(1 To 3, 1 To Item)arr(1, Item) = Cells(RowCount, 1): arr(2, Item) = Cells(RowCount, 2):arr(3, Item) = Cells(RowCount, 3)End IfEnd SelectNext RowCount'如果变量大于 1,则表示已查经查到目标,那么对数组的第一列添加标题If Item > 1 Thenarr(1, 1) = "姓名": arr(2, 1) = "成绩": arr(3, 1) = "学号"ListBox1.ColumnCount = 3'默认显示 3 列,关设置其每列的宽度ListBox1.ColumnWidths = "50,30,60"ListBox1.List = WorksheetFunction.Transpose(arr) '转置后赋与列表框End IfMe.ComboBox1.SetFocusEnd Sub以上过程在文字框的“TextBox1_Exit”事件,表示在单击回车键后触发,从而可以减少一个“确定”的命令按钮。查找过程中区分三种方式,如果复合框中选择“姓名”,则按模糊查找方式将工作表 A 列的姓与文字框中的输入字符进行比较,相同则加入数组。最后将数组转置再导入到列表框,而成绩查询则只支持精确查找,学号查询支持模糊查找。Private Sub TextBox1_Change()ListBox1.ClearEnd Sub以上过程'表示文字框中输入新的查找对象时,清空列表框中上一次的结果。(4)保存工作簿,运行窗体,复合框中保持默认值,单击回车进入文字框,输入“赵”,表示查找所有姓名中包含“赵”的同学的资料,图 16.12 是查询结果。如果将复合框修改为“成绩”,那么仅仅支持精确查找,图 16.13 是成绩查询结果:图 16.11 成绩表图 16.12 以姓名为查询条件件本例文件参见光盘:..\ 第十六章\高级查询.xlsm图 16.13 以成绩为查询条Excel VBA 程序开发自学通16.2.32020-2-26第 483页 /共 508页分类汇总捐赠额并按钮导出〖案例要求〗:工作表中有捐款者姓名、款项与捐赠日期等资料,其中部分人捐赠多次,见图 16.14 所示。现要求对捐赠者进行汇总,且可以按需求随意导入汇总结果。〖实现步骤〗:(1)插入一个窗体,将窗体的“Caption”属性设置为“汇总并导出结果”;(2)在窗体中添加两个命令按钮,“Caption”属性分别修改为“汇总”与“导出”;(3)在窗体下方添加一个列表框,用于存放汇总结果;(4)图 16.14 捐款表本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm16.2.4奇偶行列选择工具〖案例要求〗:〖实现步骤〗:本例文件参见光盘:..\ 第十六章\多工作表录入.xlsmExcel VBA 程序开发自学通16.2.52020-2-26背景着色工具箱〖案例要求〗:〖实现步骤〗:本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm第 484页 /共 508页Excel VBA 程序开发自学通2020-2-26第 485页 /共 508页第十七章 表单控件与 ActiveX 控件17.1 表单控件17.1.1控件的调出方式17.1.2表单控件功能一览17.1.3表单工具的优缺点17.1.4用单选框控制图表17.1.5用滚动条控制生产表数据17.2 ActiveX 控件17.2.1ActiveX 控件功能一览17.2.2利用组合框突破数据有效性的单列限制17.2.3在工作表中显示 Flash 动画17.2.4在工作表左上角播放 GIF 运画17.2.5在组合框显示数据源的唯一值入室篇:文件管理、菜单、API、VBE 与加载项Excel VBA 程序开发自学通2020-2-26第 486页 /共 508页第十八章 VBA 命令处理文件18.1 认识文件处理内置命令18.1.1Open 与 Close18.1.2Input#18.1.3ChDir 与 ChDriver18.1.4FileCopy18.1.5FileDateTime18.1.6FileLen18.1.7GetAttr 与 SetAttr18.1.8Kill18.1.9MkDir 与 RmDir18.1.10Name18.2 文件操作案例18.2.1在 D 盘批量建立文件夹18.2.2判断文件是否存在Excel VBA 程序开发自学通2020-2-26第 487页 /共 508页18.2.3删除 2009 年 1 月 1 日以前的所有文件18.2.4罗列指定文件夹下隐藏文件18.2.510 分钟后文件自杀18.2.6删除 D 盘下所有空白文件19.2.7文件批量重命名19.2.8将当前工作表数据导出为 TXT 文件第十九章 使用 FileSystemObject 和WScript19.1 认识 FSO19.1.1FSO 定义与用途19.1.2FSO 常用对象19.1.3FSO 常用对象的方法19.2 用 FSO 处理文件与目录19.2.1罗列 D 盘文件夹目录19.2.2在当前文件的父目录创建文件夹Excel VBA 程序开发自学通2020-2-26第 488页 /共 508页19.2.3查检 E 盘是否存在空目录19.2.4批量命名文件夹19.2.5创建文件夹文件并检查是否存在同名文件19.2.6每 D 盘下“生产表”文件夹备份19.3 关于脚本语言 WScript19.3.1关于脚本语言19.3.2WScript 的常见对象19.3.3WScript 的方法19.4 脚本语言在文件管理中的运用19.4.1在桌面建立当前工作簿的快捷方式19.4.2将当前工作簿添加到收藏夹19.4.3将 Excel 2003 和 2007 添加到“发送到”文件夹19.4.4运行屏保程序19.4.5显示 D 盘“生产表”之目录树19.4.6显示 D、E、F 盘文件夹列表Excel VBA 程序开发自学通2020-2-26第 489页 /共 508页19.4.7在注册表记录当前工作簿打开次数19.4.8新建记事本且录入字符串19.4.9打开网上邻居19.4.10打开 Excel 选项之高级选项卡19.4.11罗列名称包括 Excel 的文件夹列表19.4.12自启动软件列表19.4.13罗列所有隐藏文件夹第二十章 磁盘与系统信息管理20.1 获取磁盘信息20.1.1FSO 法20.1.2脚本法20.1.3DOS 法20.2 获取系统信息20.1.1罗列当前系统进程20.1.2计算机名与操作系统版本号Excel VBA 程序开发自学通2020-2-26第 490页 /共 508页20.1.3获取主板、显卡与硬盘信息20.1.4获取显示设置20.1.5获取网卡设置20.1.6获取 CPU 序列号20.1.7将“我的电脑”修改为实际名称20.1.8利用系统信息提升工作表安全第二十一章认识 Excel 的内置命令栏对象21.1 关于内置命令栏21.1.1Excel 对命令栏的处理方式21.1.2内置命令栏的分类21.1.3自定义快速访工具栏21.2 了解 CommandBars 集合21.2.1CommandBars 的常用属性21.2.2CommandBars 的方法Excel VBA 程序开发自学通2020-2-26第 491页 /共 508页21.2.3获取 CommandBars 子对象的名称与类型21.2.4获取及保存内置图标第二十二章创建新工具栏22.1 创建与删除工具栏按钮22.1.1建立工具栏基本语法22.1.2自定义新工具栏案例22.1.3控显示新工具栏显示方式22.2 弹出式工具栏22.2.1什么是弹出式工具栏22.2.2创建一个弹出式工具栏22.2.3创建三级工具栏22.3 特殊的工具栏(工作表目录、查找)22.3.1创建可读写的弹出式工具栏22.3.2利用工具栏文字框查找数据22.3.3切换零值、图像、分页符和批注的显示状态Excel VBA 程序开发自学通22.3.42020-2-26第 492页 /共 508页工作簿标签设计第二十三章创建新菜单栏23.1 菜单订制基础23.1.1菜单的分类23.1.1生成菜单基本语法13.1.1设计菜单注意事项23.2 设计多级菜单23.2.1多级菜单基本思路23.2.2创建一个弹出式工作表菜单23.2.3让菜单适应 Excel 2003 和 200723.2.4可定制显示方式的菜单23.3 设计感应菜单23.3.1在指定工作表才可用的菜单23.3.2工指定区域才可用的菜单23.3.3用一个菜单控制其它菜单的状态Excel VBA 程序开发自学通23.3.42020-2-26第 493页 /共 508页选择图表才出现的菜单第二十四章操作快捷菜单24.1 认识快捷菜单24.1.1快捷菜单的分类24.1.2不同快捷菜单的 VBA 表示法24.1.3Excel 2003 和 2007 中快捷菜单的差异24.2 定制快捷菜单24.2.1在右键中生成工作表目录24.2.2生成不受限的快捷菜单24.2.3快捷菜单的任意调用24.2.4在窗体中显示快捷菜单第二十五章认识类和类模块25.1 类模块基础25.1.1类模块应用范围Excel VBA 程序开发自学通2020-2-2625.1.2类与类模块25.1.3类模块代码基本步骤第 494页 /共 508页25.2 类的应用25.2.1新建工作簿时命名所有工作表(应用程序级别事件)25.2.2让零值、图像、分页符和批注切换工具提升为工作簿级25.2.3全自动转换单元格为首字母大写25.2.4开发一个颜色拾取器第二十六章API 基础与 API 应用案例26.1 API 理论26.1.1API 概述26.1.2认识 DLL 文件26.1.3API 中的数据类型Excel VBA 程序开发自学通26.1.42020-2-26第 495页 /共 508页声明 API 函数26.2 API 应用26.2.1获取计算机名和登录用户名26.2.2防 PotoShop 设计彩蛋26.2.3按任意地方都可拖动的窗体26.2.4设计圆形动画窗体26.2.5限制鼠标在窗体内移动26.2.6自由拖动改变窗体大小26.2.7渐进式出现与退出的窗体第二十七章VBA 与注册表27.1 VBA 对注册表的控制方式27.1.1什么是注册表27.1.2VBA 操作注册表的方法27.1.3VBA 操作注册表的优缺点27.1.3借用脚本实现注册表的自由控制Excel VBA 程序开发自学通2020-2-26第 496页 /共 508页27.2 注册表的应用27.2.1记录当前工作表最后一次打开时间27.2.2借助注册表限制工作簿使用次数27.2.3让程序自动调用上次的设置(零值切换)VBE 的对象模型与对象第二十八章控制28.1 准备工作28.1.1设置 Excel 选项28.1.2引用对象库28.2 认识 VBE 的对象模型28.2.1VBE 对象模型的层次结构28.2.2VBE 对象介绍28.2.3VBE 对象与 Excel 程序的关系28.2.4如何引用 VBE 对象28.2.5罗列当前工程中所有组件及其类型Excel VBA 程序开发自学通2020-2-26第 497页 /共 508页28.3 VBE 对象的控制28.3.1罗列指定模块中所有过程名称28.3.2计算代码总行数28.3.3利用代码添加/删除模块28.3.4用代码添加工作簿事件代码28.3.5用代码新建工作表且写入工作表事件28.3.6删除当前工作簿所有代码28.3.7导出当前工作簿所有 VBA 代码28.3.8用代码生成窗体与控件第二十九章VBE 的高级运用29.1 菜单定制基础29.1.1认识命令栏对象29.1.2生成菜单基本语法29.1.3罗列 VBE 中所有菜单与子菜单29.1.4生成菜单条与右键菜单Excel VBA 程序开发自学通2020-2-26第 498页 /共 508页29.2 开发 VBE 插件百宝箱29.2.1开发插件的准备工作29.2.2开发代码编号工具29.2.3开发代码美化工具29.2.4开发代码清除工具29.2.5开发代码减肥工具29.2.6编写菜单29.2.7生成插件第三十章 加载宏与加载项概述30.1 关于加载宏30.1.1加载宏的特点30.1.2为什么使用加载宏30.1.3加载宏管理器30.1.4内置加载宏的加载与使用30.1.5安装自定义加载宏Excel VBA 程序开发自学通2020-2-26第 499页 /共 508页30.2 关于加载项30.2.1加载项的分类30.2.2加载项的开发方式30.2.3两种加载项的安装方式第三十一章利用 VBA 编写 XLAM加载宏31.1 开发前的准备31.1.1xla 与 xlam 的区别31.1.2生成加载宏的基本步骤31.1.3开发加载宏与普通 VBA 编程的区别31.2 开发集公农历一体的日历输入器31.2.1确认程序需具备的功能31.2.2定义公历转农历的函数31.2.3设计日历输入器窗体31.2.4编写窗体初化代码Excel VBA 程序开发自学通2020-2-2631.2.5实现输入器与工作表交互31.2.6设计帮助31.2.7定制菜单31.2.8发布插件第三十二章第 500页 /共 508页利用 VB6.0 编写 COM 加载项32.1 COM 加载项开发基础32.1.1安装 VB 6.0 企业版32.1.2添加引用32.1.3开发 COM 加载项基本步骤32.2 开发重复值制器32.2.1确认插件所需功能32.2.2建立 VB 工程32.2.3编写菜单代码及响应事件32.2.4编写重复值控制主程序Excel VBA 程序开发自学通32.2.52020-2-26第 501页 /共 508页发布加载项并安装调试攀峰篇:开发“Excel 百宝箱”第三十三章程序开发思想33.1 开发人员自我定位33.1.1区别开发人员与应用人员33.1.2开发人员基本条件33.2 如何开发最佳应用程序33.2.1罗列应用程序需具备的功能33.2.2与终端用户交流33.2.3规划程序结构33.2.4设定友好的界面33.2.5提升程序通用性第三十四章开发“Excel 百宝箱”34.1 程序规划Excel VBA 程序开发自学通2020-2-2634.1.1了解终端用户需求34.1.2确认插件功能表34.1.3规划插件结构第 502页 /共 508页34.2 安全工具箱34.2.1多工作表加密解密34.2.2设置允许编辑区34.2.3工作表反向加密34.2.4保护公式34.2.5禁用磁盘34.3 财务工具箱34.3.1制作工资条头34.3.2根据工资计算所需钞票张数34.3.3小写金额转大写34.3.4大写金额转小写34.3.5工作簿Excel VBA 程序开发自学通2020-2-2634.3.6工作表拆分34.3.7工作簿拆分34.3.8复选框工具34.3.9文本与数据转换第 503页 /共 508页34.4 打印工具箱34.4.1分页小计34.4.2打印当前页34.4.3双面打印34.4.4底端标题34.5 合并工具箱34.5.1合并同行数据34.5.2合并数据并复制34.5.3取消区域合并填充原合并值34.5.4可还原的合并居中34.5.5合并列中相同数据Excel VBA 程序开发自学通34.5.62020-2-26第 504页 /共 508页取消列中合并且还原数据34.6 批注工具箱34.6.1批注管理器34.6.2添加个性化批注34.6.3建立图片批注34.6.4批量添加右列内容为批注34.6.5批量导入同名照片到批注34.7 图表工具箱34.7.1批量修改标签34.7.2批量移动标签34.7.3图表输出为图片34.8 图片工具箱34.8.1导出图形到硬盘34.8.2批量导入图片34.8.3单元格转换为图片Excel VBA 程序开发自学通2020-2-26第 505页 /共 508页34.9 不重复值工具箱34.9.1提取唯一值34.9.2清除列中重复值34.9.3不能输入重复值34.9.4筛选唯一值34.9.5突出选区重复值34.9.6标示列中重复值34.9.7产生不重复随机数34.10 文件工具箱34.10.1新建文件夹34.10.2新建工作表34.10.3查找文件并打开34.10.4批量重命名34.10.5建立文件目录34.11 系统工具箱Excel VBA 程序开发自学通2020-2-2634.11.1锁定屏幕34.11.2查看电脑使用时间34.11.3查看磁盘信息34.11.4网卡 IP 与 CPU 的 ID 号34.11.5查看程序使用端口34.11.6清理垃圾文件第 506页 /共 508页34.12 选择工具箱34.12.1行列选择工具箱34.12.2选择本表图片34.12.3区域定位工具34.12.4反向选择34.13 程序员工具箱34.13.1生成系统图标34.13.2取得所有菜单 ID34.13.3提取所有代码到工作表Excel VBA 程序开发自学通2020-2-2634.13.4删除所有 VBA 代码34.13.5一键修复 Excel第 507页 /共 508页34.14 其它工具箱34.14.1隐藏非使用区34.14.2生成字母与百家姓序列34.14.3一键删除超链接34.14.4一键删除工作簿数据链接34.14.5隔行插入行34.14.6选区字符统计34.14.7批量上标34.14.8七彩文字34.14.9工作表管理器34.15 开发函数34.15.1开发函数34.15.2设计函数帮助Excel VBA 程序开发自学通2020-2-26第 508页 /共 508页34.16 定制百宝箱帮助34.16.1定制底端标题帮助34.16.2定制百宝箱帮助34.16.3邮件返馈34.17 定制多级菜单并发布34.17.1定制菜单34.17.2发布34.17.3测试34.17.4小结

ExcelVBA程序开发自学通2020-2-26第1页/共508页为入门篇:VBA优
ExcelVBA程序开发自学通2020-2-26第2页/共508页=TEXT(RIGHT(19MI
ExcelVBA程序开发自学通2020-2-26第3页/共508页图1.3自定义函数
ExcelVBA程序开发自学通2020-2-26第4页/共508页图1.4插件法批量
还剩 504页未读,点此继续全文在线阅读

免费下载Excel-VBA-从入门到精通必备到电脑,使用更方便!

本文推荐: Excel-VBA-从入门到精通必备.pdf全文阅读下载  关键词: Excel   入门   精通   必备  
学文库温馨提示:文档由用户自行上传分享,文档预览可能有差异,下载后仅供学习交流,未经上传用户书面授权,请勿作他用。 文档下载资源交流QQ群:317981604

Excel-VBA-从入门到精通必备pdf目录

文档相关搜索

< / 508>

QQ|小黑屋|网站声明|网站地图|学文库 ( 冀ICP备06006432号 )

GMT+8, 2020-7-7 03:55 , Processed in 1.293020 second(s), 5 queries , Gzip On, Redis On.

Powered by 学文库 1.0

Copyright © 2019-2020, 学文库

返回顶部