为入门篇:VBA优势、功能与概念
从Excel插件认识VBA
简单的说,Excel VBA是依附于Excel程序的一种自动化语言,它可以使常用的程序自动化,类似于DOS(磁盘操作系统)中的批处理文件(后缀名“.bat”)。那么它有什么具体的功能?在工作中与常规操作方式相比,具有哪些优势?笔者试图通过一个简单却实用的插件来展现。
本章要点:
从身份证号获取个人信息
在工作中如何发挥Excel插件的优势
从身份证号获取个人信息
制作人事资料时,通常需要录入职员身份证号码,以及生日、年龄、性别等等。除身份证号码需要手工逐一录入以外,其它三项信息的录入有四种方法:手工录入、内置公式、自定义函数法、插件法。手工输入方式效率极差,且出错机率也最高,本节通过后三种方式来实现并比较,从而让读者对VBA之优势与用法得以初步认知。
常规公式法
以图数据为例,利用公式从身份证中提取生日、年龄、性别等信息,可以有多种方法。本例列举其中之一。
图 根据身份证号提取职工年龄、生日与性别
通过公式计算职工的年龄、出生日期与性别,步骤如下:
(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输入以下公式,用于计算出生日期:
=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区域,将公式向下填充即完成身份证信息提取。效果如下:
图 公式法获取身份证信息
点评:相对于手工输入法,利用公式从身份证号码获取个人信息有着效率更高、错误率更低之优点,人员越多时越能体现出其高效优势。
本例文件参见光盘:..\ 第一章\提取身份证信息.xlsm
自定义函数法
自定义函数是指利用VBA编写的外置函数。在本例的随书光盘中已经录入了相关的VBA代码,可以随时调用。对于代码的含义和录入方式在后面的章节后有详细介绍,本章仅通过具体应用了解其用法与优势。具体操作步骤如下:
(1)进入“自定义函数法”工作表;
(2)在C3:E3区域分别输入以下三个公式,用于计算年龄、出生日期和性别:
=SFZ(B3,"NL")
=SFZ(B3,"SR")
=SFZ(B3)或者=SFZ(B3,"XB")
(3)选择C3:E3单元格,将公式向下填充,结果见图所示。
图 自定义函数法获取身份证信息
本例中的函数SFZ即身份证函数,用于从身份证号码中获取年龄、生日与性别等信息。它不属于Excel内置函数,需要利用VBA编写代码才可以使用。读者可以从随书光盘中获取该完整代码。
SFZ函数有两个参数,第一参数为单元格引用,第二参数为信息描述,即用于指定需要获取身份证中哪一部分信息。当它为“NL”(不区分大小写)时,获取年龄;当它为“SR”时,获取生日,当它为“XB”或者省略第二参数时,获取性别。
点评:相对于内置函数法/公式法,自定义函数法是借用VBA编写的外置函数完成,它的优势在于公式简短,且容易理解。任何不熟悉函数与VBA者皆可一分钟内学会操作并理解其公式含义。
插件法
插件法是指借用Excel插件操作工作表,该插件不隶属于当前工作簿,但却可以实现与当前工作簿交互的功能,批量、迅速完成身份证信息提取工作。
操作步骤如下:
(1)关闭Excel程序的前提下,将随书光盘中的插件(位置:..\第一章\批量获取身份证信息.xlam)复制到以下自启动文件夹中即安装完成:
C:\Program Files\Microsoft Office\Office12\XLSTART
注意:如果您的OFFICE没有装在C盘,那么上面的磁盘号需要根据实际情况做修改;如果您使用OFFICE 2003,则将其中“Office12”修改为“Office11”。
(2)打开光盘文件“提取身份证信息.xlsm”,进入“插件法”工作表;
(3)选择单元格区域B3:B6,单击右键,从右键中选择【批量获取身份证信息】菜单,程序将弹出一个对话框“确定计算区域”。该对话框中默认显示当前选区地址,如果需要修改地址,可以输入新的地址,也可以用鼠标在工作表中选择身份证存放区域,该区域的地址会自动产生在对话框中。见图所示;
(4)单击“确定”按钮,程序在瞬间就会从选区的所有身份证中提取年龄、生日和性别等信息。
图 插件法批量获取身份证信息
点评:插件法从身份证号码中获取信息的优点是速度快,通用性好。相对于内置函数法,它在操作上更简单,不需要任何函数知识,不需要输入长长的公式,只点几次鼠标即可;相对于自定义函数,它的优点是通用性好,在任何工作表、任何工作簿皆可使用本工具。而前一方法之自定义函数非插件方式存在,只能在当前工作簿中使用。
浅谈VBA优势
前面三个案例中我们可以看出,Excel具有强大的计算功能,但常规方式对于某些大型数据运算显得比较繁琐。用户需要学习复杂的函数知识,设置长长的公式才可以解决某些运算。而VBA可以使公式简化、易懂,甚至根本不需要公式,一个字母不用录入即可完成一些专业性较强的计算。
具体说来,相对于Excel自带的功能,VBA或者说VBA开发的插件具有以下优势:
批量地对操作对象进行数据处理
以前一节插件法完成身份证信息进行例证,它可以瞬间完成多个单元格数据的运算,甚至多个工作表中存放的身份证号码也可瞬间完成信息提取。较传统的逐一处理方式在效率上有大幅提升。
多任务一键完成
多任务是指对同一个对象需要进行多个操作,例如前一节是从身份证号码中获取三类信息,VBA可以单击一个按钮后瞬间完成,完全感觉不到它在分三步逐一完成任务。这是高效办公地最佳体现。
将复杂的任务简化
Excel是很多很多小工具的综合体。这些工具可以嵌套运用,完成更强大的数据处理。但当嵌套过多时,就需要用户要较深的功底才能操纵或者理解。另一方面,对于某些特殊行业的工作、任务,也要经过很复杂的操作才可以完成,而对于某些只需要应用不需要深入研究、理解的普通办公文员们来说是一个技能考验。而通过VBA进行二次开发可以将复杂的任务变得更简单。简单是指理解和操作上同时简化。
就像节中通过右键菜单提取身份证号码三类信息一样,不需要用户去录入长长的公式,以及理解信息是如何提取出来的,单击菜单即可完成。再如企业中生成工资条,10000个人的资数用手工操作需要处理10000*N次,而利用Excel插件可以单击按钮完瞬间成。
将工作表数据提升安全性
利用VBA代码可以对数据进入多层保护,在某些特殊需求下,VBA可以保护数据让普通用户无法胡乱修改,或者不小心破坏数据及数组结构。
提升数据准确性
准确性体现在数据录入和数据运算两方面。首先,通地VBA对输入的数据进入限制,可以防止用户意外录入不规范字符。如数字中有两个小数点,或者录入数值时不小心录入了标点或者字母,造成无法计算或者漏算。其次,在数据运算时,人工设置大量公式,或者每天在不同地方重复录入同一个公式。在大量地操作中难以避免不产生一次错误。而利用VBA可以让工作简化,工作量越小,出错的机率一定越小;同时,在大量重复性工作中VBA可以确保不产生错误。
完成Excel本身无法完成的任务
弹出提示、警告对话框、行程安排与预告,或者到磁盘中查找需要的数据、修改注册表等等,Excel常规方式是不可能完成的。如果需要类似功能,VBA完全可以胜任。
开发专业程序
利用VBA还可以开发一些专业型的程序,如报表汇总软件、进销存管理系统、人事管理系统等等,可以将界面设置成其它任何软件的显示方式媲美专业的程序软件
插件特点及其如何发挥插件的优势
在前一节中,通过一个身份证信息获取的插件认识了Excel插件,那么在工作中应如何发挥Excel插件的优势呢?
Excel插件的特点
Excel插件是利用VBA程序开发的外置工具,通常是xla、xlam格式或者dll格式。其中xla和xlam插件直接用Excel就可以开发,而dll插件通常采用VB或者C++来编写。
不管何种软件开发的插件,它都需要在外观和功能两方面具有某些特征,以方便用户调用。
1.外观特征
有若干个菜单或者工具按钮
在插件封装后,调用其代码有两种方式:用代码调用,用菜单或者工具栏按钮,显然菜单更方便。用户通过菜单单击即可完成相对于常规方式较复杂的操作或者运算。
利用窗体实现与工作表数据交互
在弹出的窗体中可以调用工作表的数据,也可以将窗体中录入的数据导到工作表。而在窗体中录入数据时,相对于工作表中录入数据,可更好地控制。例如某个文字框中可以指定只能录入数字,而另一个文字框可以指定只能入日期。也可以设定录入某项目后自动跳转到指定目标位置,而不用手动去移动光标插入点。甚至可以在录入时核对是否与工作表中数据是否重复等等……
有一个帮助界面
对于开发者来说,不管自己开发的工具如何简单,都有必要向用户说明其功能和操作方式。所以在工具中通常加入一个窗体,进行文字说明或者动画演示。特别是工具没有提供菜单、而是通过函数调用或者快捷键调用时,更需要一个说明窗体。
对函数做参数说明
对于函数类插件,必须对每个函数的参数进入详细说明,让用户插入函数时可以清晰明了地看到每个函数中每个参数的功能与使用方式。
2.功能特征
Excel插件中的代码和普通宏程序的代码在编写上具有一些差异,这是它们的设计目的不同造成的。其中宏代码通常用于解决某个具体的问题,它可能限用一次,也可能需要反复调用。但都只为解决自己的某个具体问题而录制。而开发Excel插件则通常是开发者开发后,给其他的终端用户使用。用户不确定,需要操作的区域对象不确定。所以插件有不同的需求,它需要具有以下特征:
没有具体的区域地址
由于开发插件通常是给其他的终端用户使用,所以不能指定数据区域地址,而是提供一个自由选择目标区域的选择对话框,或者利用代码计算目标工作表中的待计算区域。这是和录制宏最大的差异。
不使用具体的工作表名或者工作簿名
原理与前一条一致。
必须有通于菜单或者窗体供用户调用命令,而不是在工作表中建立按钮来调用命令。
dll格式的插件不存在工作表,而xla和xlam格式插件的工作表是隐藏状态,工作表不可能在用户的界面呈现出来,所以必须建立一个通用的菜单栏,使其在打开任意工作簿都会显示出来供用户操作。如果使设置了快捷键,那么是可以不用菜单或工具栏的,界面将会更简洁。
尽可能提供自定义选项
插件的针对性不强,即它需要有广泛性。插件通常不是为某一个固定用户开发,或者需要处理的数据并非永远一致,那么在不同用户使用同一功能时,需要有自定义其参数或者选项的空间,工具才能有更好的通用性。例如设计一个工资条制作插件,那么工资条的表头行数就有必要让用户选择,而非强制一行或者两行。这和编写一个解决临时性问题的编程思路不同。
具有多版本适应能力
目前办公用户使用的Excel版本差异很大,有Excel 2000、Excel XP、Excel 2003,也有Excel 2007。开发者不会假定用户都用某个版本的Excel,而是通过代码判断当前用户的版本号,然后调用不同的代码,以适应当前版本,否则某些功能可以无法使用。
防错机制
自用型宏程序通常不用防错,因为用户和开发者是同一人。而插件则必须有完善的防错机制,预先设置了遇到某种错误该如何反应的措施,避免破坏用户数据,或者进入死循环,消耗尽计算机的内存资源。
Excel插件的优势与限制
在工作中使用插件,可以使用工作更轻松,运算更快速、准确。当然前提是插件的代码编写足够优秀,不仅具有很强的通用性,还要有完善的防错机制,以及灵活的自定义选项。那么工作中使用优秀的插件进行工作具有哪些优势呢?
简化操作:类似于bat批处理文件,可以一键执行多个任务
强化功能:对Excel内置功能无法完成的一些任务,借用VBA代码可以实现
美化界面:VBA用以调用Flash动画,也可以播放Gif动画,还可以直接对单元格字符产生滚动效果。对于喜欢装点的用户,借用VBA可对工作表进行很好地修饰
固化格式:VBA可以对录入的数据进入检测,阻止输入不规范的数据;也可以禁止新增、删除工作表,或者禁止缩放窗口,从而促使多用户文件能确保格式一致,便于汇总
虽然插件在工作中有以上优势,但它在某些方面也具有一些限制:
通用性方面:开发插件通常是个人行为,而非Office软件一样由一个大公司主持。所以其通用性很可能不是很好,开发者测试的次数少以及测试条件不足等等,导致工具具有某些隐含的缺陷
防错方面:程序员不一定是终端用户,甚至可能从来没有成为办公用户,而是直接学习插件开发。那么在程序编写时就可能思维受限,无法对可能出现的所有错误进行防范
移植方面:插件属于外置工具,它的所有功能都需要安装才能使用。所以如果利用插件设计的表格有可能传用客户后无法正常开启,或者开启后无法正常显示。最好的解决方法是将插件让客户端也安装一次
独立方面:Excel的VBA是依附于Excel主体程序的附属程序,它可以开发强化Excel功能的程序,但不能开发脱离Excel而单独存在的软件。如果需要开发全新而专业的应用程序,VBA并非理想的程序
如何发挥插件的优势
可以确定的是,善用插件可以提升工作效率。但是插件也不可滥用,否则享用优势的同时,也会产生一些后患。
首先,需要明白插件相对于Excel的功能属于外置工具,它需要安装后才能使用。如果读者的文件非自用型,需要与他人共享、阅读,那么需要连插件一起共享;
其次,如果是简单的功能,尽量使用内置功能,少用插件。插件适用于处理复杂的或者Excel内置功能无法完成的工作;
宏有一个通用BUG,即使用宏代码后,内置撤消功能将禁用。为了让用户减少损失,针对某些会更新数据、修改(破坏)原有格式的工具,一定要提供一个恢复原状的程序。例如有制作工资的工具,就搭配一个删除工资条的工具。
最后,尽量将插件在同部门共享。即一个办公室为单位或者一个企业为单位,让整个单位都拥有相同的插件,才能更好地发挥插件优势。
开发Excel插件的条件
针对插件的开发者,他/她需要什么条件呢?现罗列如下:
熟练撑握VBA技术
这是首要条件。必须对大部分常用对象及其属性熟练地掌握。且需要了解数据处理的常用方式,并从多种处理方式中找出最高效且通用的方式。如果在某些特殊情况下,程序的通用性与执行效率只能选择其一时,通用性优先于执行速度。
具有一定的报表操作经验
仅学习VBA是可以熟练掌握VBA知识的,但是仅掌握VBA知识却不可能成为优秀的程序员。例如开发财务人员用的插件,那么需要懂得一些财务知识,不需要精通,但一定要对财务知识有所了解或者有财务报表的制作经验,才可能开发出适合于财务人员的插件。
美化常识
这里的美化并不一定是漂亮的外观,而是要使自己开发的程序界面具有协调性、统一性,还需要了解普通用户的操作习惯,根据习惯设计人性化或者操作更便利的界面。当然,在不影响效率的前提下,将窗体设计得更美观,也是具有现实意义的。
熟悉不同版本的Excel间的差异
终端用户们有可能使用多个版本的Excel,那么开发者也需要了解不同版本间的差异。例如Excel 2003中Application有一个属性FileSearch,用于在磁盘中查找文件,而Excel 2007取消了该属性,那么开发插件时就应尽量避免使用该属性,借用其它方法的代替。否则将产生兼容性问题,以致程序产生BUG。
具有较强的耐心
编写程序是一个与字母相处的过程。对于大中型程序,可能长时间对着一堆字母或者数字,这需要有一定的忍耐力。甚至在程序开发完成后,仍然需要耐心对程序进行多角度、多版本的测试,以提升程序的通用性和纠错性。
本书架构
本书除VBA基本理论外,偏重于讲解插件开发的原理、思路与方法以及如何提升程序速度。在以后的章节中,主要从按以下方式进行编排:
VBA历史与功能、安全性等等周边知识简要介绍
认识VBE编辑器并对其它进行优化设置
学习VBA中常用对象及属性、方法、事件
VBA代码如何提升执行速度
掌握VBA高级应用,包括窗体的认识,及磁盘、目录与文件操作
开发VBE环境下的插件
学习利用VB开发专业性的COM加载宏插件
最后利用前面章节的知识开发一个大型Excel插件。从该插件的开发思路和过程让读者了解插件开发的常规流程及注意事项
本书以插件开发为重心,但对于VBA中常用知识,不一定与插件开发相关的知识但工作中较常用的功能也会进行详解,或者进行实例演示。
除插件开发外,程序的提升和防错在本书的多次强调的重点。
从第二章开始,让读者学习、掌握VBA理论知识,为插件开发提供基础。
第二章 VBA概述
VBA是VB(VISUAL BASIC) 的一个子集,是一种附属于Excel的程序软件。在学习VBA的语法之前有必要对其发展史、功能、特点等等方面进行了解。
本章要点:
VBA的发展史与优缺点
VBA能做什么
VBA的安全性
使用VBA帮助
VBA的发展史与优缺点
VBA语言作VB家族成员,起步很早。发展至今已拥有非常广大的用户群,在日常办公中有着举足轻重的作用。
宏与VBA
Excel早在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,是自动执行某种操作的命令集合。它包括两个过程,即Excel 4或者称为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程序的大部分代码,程序员仅需在录制的宏代码中稍加修改即可成为最后的合格程序;另一个最重要的因素是录制宏可以为程序员提供词典的作用,即忘记了某个对象单词,或者完全不明白某个属性的语法时,利用录制宏可以产生对应的代码,用户复制即可使用。
VBA历史与版本
VBA的前身是xlm宏语言,鉴于xlm宏功能有限,至今已经用VBA完全替代了xlm宏。但是为了体现兼容性,所有版本的VBA中皆可以调用以前的部分宏表函数。例如Excel 2007的application对象仍然保留了以下宏表相关的一个方法和两个属性,通过它们可以执行早期宏表所有函数:
在抛弃早期宏语语言后,VBA从1993年开始逐步在很多软件中出现,除OFFICE办公软件外,Cad、Coreldraw等等软件也支持VBA。目前VBA的最高版本是。但需要申明的是,VBA版本并非与与主体程序的版本对应升级,即Excel的多个版本有可能使用同一版本的VBA。如OFFICE 2003和OFFICE 2007都使用版的VBA。
检测当前OFFICE中VBA版本可以使用以下代码:
Sub 获取VBA版本号()
MsgBox
End Sub
不同版本的VBA带有不同的函数,编程时需要根据VBA的版本调整体码,使之尽量通用。但在Excel中编写VBA程序时,Excel的版本号显得更为重要。因为不同的Excel版本有不同的对象和方法,而且差异较大。在本书的附录中有Excel 2007与早期版在VBA方面的差异,做为插件开发者有必要进行全面了解。
VBA优、缺点
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用户与用户之间的交流。国内、国外都有很多大型的VBA相关论坛,可以通过论坛交流心得、学习他人的编程思路,以及在线提问。OFFICE VBA方面的论坛远比C++与.net等等语言的相关论坛更多。
相对于Excel内置功能,VBA也有它自己的缺点:
学习周期长
学习VBA的时间至少两个月,而数据透视表、函数、图表等等其它内置功能则相对更快。
专业词汇多
VBA中有几百个对象,每个对象有多个属性以及方法,虽然不需要死记硬背所有对象名称和属性,但仍然需要花很多精力来理解、消化。
普及范围小
目前VBA用户群在一天天扩大,但相对于Excel的内置功能如公式、图表等等,仍然有待进一步提升普及率。在普及不够的情况下,程序员的插件需要做更完善的帮助系统,也需要更多的时间来测试,使未接触VBA的用户能更快地掌握其技巧。
VBA能做什么
VBA是一门程序语言,工作中VBA的常见用途是什么?本节进行讨论。
VBA用途
可以肯定地说,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可以编写很专业的程序,且能对其进行保护,以确保开发者的利益。即使是纯公式设计的报表,有时也需要借用VBA来设计程序注册功能或者登录界面等等。
VBA主要用户
根据VBA的特点,使用VBA主要有两类对象。
其中最主要的是开发者就是终端用户,即编写代码给自己用的的业余程序员。而且VBA也是所有程序中拥业余程序员最多的一类程序。利用VBA解决一个临时问题,或者处理某一个具体的重复性任务,是大家使用VBA最多的原因。只有少数用户不为解决当前问题而基于兴趣编写一些通用型插件,可以解决很多很多类似问题。
注意:普通宏程序和插件最主要的一个区别是:编写普通宏程序只为针对当前遇到的一个具体问题而编写,当前问题解决后,该程序也不再有存在的价值;而编写插件则通过针对具有大从性质的问题,例如工资条设计,很多企业、事业单位都需要,那么它的生存周期是很长的,用户也是不固定的,在编写时也就会产生更多的自定义选项留给终端用户。所以编写插件和自编自用型宏程序在困难度上有较大区别
VBA用户通常用于两个基本条件:其一为工作中某些任务利用常规方式处理太繁琐,需要VBA来简化工作;其二为因VBA兴而趣研究。笔者一直坚持的一种观点是:对VBA保持持续的兴趣是学好VBA很重要的条件。
另外一类VBA用户即为专业的VBA程序员,专为别人定制程序。这类用户除了需要熟悉VBA语法外,还需要对Excel各项操作有较多的经验。也有部分人员本身就是某个行业的资深主管、兼职程序员。例如一个精通VBA的财务主管,他/她开发的财务类VBA程序一定比只精通VBA不懂财务的专业程序员开发的程序更专业,或者说更具有易用性。
VBA的安全性
上世纪90年代宏病毒泛滥,使人们对宏以及宏的安全性都有所了解。但事实上很多用户也就通过宏毒病的传播了解宏的部分特点,这自然是很片面的。
VBA安全性
事物都有双面性,程序语言犹其如此。程序的功能越强,那么同时意味着用它做破坏也可以具有更大的破坏力。VBA依附于Excel程序,但它做为病毒传播时,可以破坏的对象却不局限于Excel程序,磁盘中所有文件皆可以任意修改。正因如此,学习VBA时,有必要掌握好这把双刃剑。
在默认状态下,Excel 2003和 2007都是禁用宏的,以确保用户数据不会因潜在的宏病毒而受破坏。但是同时也带来另一个问题——正常的VBA程序无法执行。所以通常有三种做法:
不用VBA程序的用户,彻底禁用宏,杜绝宏病毒蔓延。
常用VBA程序,包括自己开发和别人开发程序的用户,可以将宏的安全性稍加提高,即遇到宏时提示用户,当用户确定代码安全时再执行。
第三类自编自用型用户或者完全信任宏代码开发者的用户,则可以将宏的默认设置修改为无限制。即允许任何宏执行,从而提升工作的效率。笔者属于第三类,永远允许所有宏自动执行。
了解安全性对话框
Excel 2007有两个安全性对话框。一个是打开带有宏代码时提示用户的“安全选项”对话框,见图所示;另一个是位于Excel选项中的“信任中心”对话框,见图所示。
1.安全选项
当启动一个具有宏代码的工作簿或者启动一个COM加载宏时,默认状态下在Excel编辑栏上方会弹出一个“安全警告 部分活动内容已被禁用”的提示框(根据加载宏的类型不同,显示的文字也会有所差异)。当用户单击“选项”按钮后再次弹出一个“安全选项”窗口,在窗口中将罗列出所有被阻止的加载项。本例中有两个,包括一个xla格式的加载宏,和一个dll格式的COM加载项。
如果确认需要在工作簿使用某个加载项,或者信任该加载项中的代码,则选择“启用此内容”,然后单击“确定”按钮,安全警告对话框消失,而相关加载项所携带的代码也可以任意调用了。
图 安全选项
图 Excel信任中心
2.信任中心
前面所说的每次在开启工作簿时启用自己信任的插件或者宏工作簿,虽然确保了安全,事实上效率不高,操作上较繁琐。而设置信任中心可以一劳永逸的解决上述问题。
进入信任中心步骤如下:
(1)单击左上角的圆形OFFICE按钮;
(2)选择“Excel选项”按钮打开Excel选项对话框;
(3)单击左边的“信任中心”按钮即可显示“信任中心”对话框。见图所示。
Excel选项的信任中心主要用于管理宏的安全性问题,它包括“受信任的发布者”、“受信任位置”、“加载项”、“ActiveX设置”、“宏设置”、“消息栏”、“外部内容”和“个人信息选项”8个选项,分别具有以下作用:
受信任的发布者:罗列出本系统中所有数字签名证书。证书是文档中电子的、基于加密的安全验证戳。此签名确认该宏或文档来自签发者且没有被篡改,表明您认为该数据库是安全的并且其内容是可信的。这可以帮助数据库的用户确定是否信任该数据库及其内容。
受信任位置:表示该位置下存放的代码是受信任的,不受宏的安全性设置的影响,任何情况下都会执行其代码。在本对话框中罗列了Excel默认设置下的几个位置。用户也可以手工添加或者添新的受信任位置。通常可以将自己编写的代码所存放的目录设置为受信任位置,以避免每次手工启用宏。
加载项:添加或者删除加载项,包括xla、xalm、dll和xll格式的加载项。在本对话框中可以手工安装以及删除所有格式的插件。而笔者的习惯是xla格式或者xlam格式的插件直接存到Excel自启动文件夹中,免除安装。对于COM加载项,即dll格式的插件则可以使用本对话框中手工安装。获取自动启动的路径可以使用以下代码:
Sub 自启动路径()
MsgBox
End 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是否弹出消息栏,即阻止宏运行时是否通知用户。默认状态是要发出通知。
外部内容:所谓外部内容指工作表中的公式引用发其它工作簿,包括加载宏中的数据。本选项决定是否禁止引用以及是否弹出提示。在确保数据安全的前提下尽量选择数据链接和自动更新,以提升效率。
个人信息选项:本选项包括几个与网络相关的信息,在网络不可用的情况下设置无效。而网络畅通的情况下数据引用也比较慢,建议不再启用所有选项。
让自己的VBA程序畅通无阻
根据前面的描述,Excel默认状态下是禁止宏代码和ActiveX控件运行的。而对于自己编写的完全可以信任的代码也产生了阻碍作用,这促使用户需要对其进行设置,让代码默认即可执行。
让自己的VBA程序畅通无阻有两种方式,签署数字证书和设置受信任位置。从操作程序比较,设置受信任位置是最快速、最方便的办法。下面以安装“D:\工具箱\批量获取身份证信息.xla”为例演示如何让程序自启动而不被Excel阻止。
(1)在D盘新建文件夹,命名为“工具箱”;
(2)将“批量获取身份证信息.xlam”插件复制到“工具箱”文件夹中;
(3)单击【Office按钮】\【Excel选项】\【信任中心设置】\【受信任位置】\【添加新位置】打开“Macrosoft Office受信任位置”对话框,该对话框中默认显示Office临时文件夹路径;
(4)在“Macrosoft Office受信任位置”对话框的“路径”文字框中输入“D:\工具箱\”,并勾选“同时信任此位置的子文件夹”,在“说明”文字框中输入说“我的信任位置”,见图所示:
图 设置受信任位置对话框
(5)单击“确定”按钮后返回“受信任位置”,在列表中将会显示新增加的地址“D:\工具箱”;
(6)单击“确定”按钮后返回“Excel选项”对话框,单击左边的“加载项”按钮,再在底部的“管理”下拉列表中选择“Excel加载项”,单击“转到”按钮打开“加载宏对话框”。该对话框中罗列了所有已安装的内置加载宏和外置加载宏,见图所示;
(7)单击“浏览”按钮,从对话框中选择“D:\工具箱”中的“批量获取身份证信息.xla”,然后返回加载宏对话框,可以发现插件列表中已经新增了一项:“批量获取身份证信息”,见图所示:
图 加载宏对话框 图 加载xla格式的插件
(8)经过以上设置后,重启Excel,可以看到插件已经可以正常运行,而不会弹出任何阻止宏执行的对话框。
注意:将插件直接放到自启动路径中也可以实现不弹出阻止宏运行的对话框,且默认允许所有代码运行。但是它的缺点是不在“加载宏”列表中出现,不便于随时卸载与安装。而利用“加载宏”对话框安装的工具可以在方框中打勾或者不打勾来表示安装与卸载。
使用VBA帮助
Excel的VBA具有详尽的帮助供用户学习、参考。通过帮助可以使用用户学得更快,对对象、属性、方法等也掌握更准。
利用帮助学习VBA语法
VBA有三种帮助:即时提示、本地帮助和在线帮助。
1.即时提示
即时提示是指用户录入代码时,VBA弹出的选项,也称快速信息。它可以对属性、对象、方法和参数等等进行提示,使用户可以快速而准确地录入代码。例如需要输入获取单元格地址的代码,但又无法确定记忆中的单词是否正确,就可以借用提示来完成。从快速信息录入代码不仅快速,而且绝对正确。具体步骤如下:
(1)录入代码“Range ("A1")”;
(2)继续录入一个半角状态下的小数点,程序弹出即时信息——下拉列表,其中“Address”即表示区域的地址,见图所示;
(3)按下键盘上的下箭头,移动到“Address”处再按下Tab键,完整的单词即自动追加到代码中。如果手工录入,可能会录入为诸如“Adress”或者“Addrees”等等错误代码。
如果在代码中录入函数,以及左括号时,仍然会产生即时帮助信息提示。不过不是下拉列表,而是参数所有参数的提示,及每个参数的类型,见图所示。
图 即时帮助之属性示 图 即时帮助之参数提示
2.本地帮助
本地帮助是指安装Office时安装的帮助文件,存放在本地磁盘中。
调用本地帮助的步骤如下:
(1)打开Excel 2007,利用快捷键【Alt+F11】打开VBA编辑器;
(2)按下快捷键【F1】打开VBA帮助窗口。窗口的标题是Excel帮助,和在工作表中按下【F1】调出的帮助窗口标题一致,然而其功能却大大不同。
(3)在帮助窗口右下角可以看到“脱机”二字,表示当前调用的帮助信息是本地帮助。在左上角的的文字框中输入待了解的信息“names”,并单击回车或者左键单击“搜索”按钮,立即出现关于“names”的所有帮助信息,包括“names”的对象、方法、属性和实例。从窗口中可以看出关于“names”的帮助有30条,本页仅显示1到25条;
图 调用names的本地帮助
(4)单击其中第三项“”,窗口中立即出现工作簿级名称集合的相关帮助信息,包括语法、说明和示例,见图所示;
(5)如果需查看关于“names”的其它帮助,则单击工栏具的图标返回,再从列表中选择目标帮助信息。也可单击“下一页”按钮查看其它帮助。
图 的帮助
3.在线帮助
在线帮助是指来自网络的在线信息。单击帮助窗口右下角的“脱机”,将弹出“连接状态”提示框,见图所示。从中选择“显示来自office online的内容”即可调用在线帮助。
在图中显示了关于“names”的在线帮助。
图 调整为显示在线帮助 图 显示关于names的在线帮助
当然,除了上述三种帮助,可以有到国内外的Office论坛获取专家们的在线指导。例如Excelhome论坛()、Officefans论坛(
捕捉错误
VBA对于代码出错时具有良好的捕捉功能,可以提示用户代码有误,甚至给出错误类型、突出标示错误语句,对于部分错误还会提示或者定位于出错的代码上,使用户可以及时更正。
错误捕捉体现在编译错误和运行时错误两方面:
1.编译错误
编译错误指在VBA内部无法编译代码时产生的错误,大部分时候在编写代码时就能出现编译错误的提示,而不需要等到执行代码。
产生编译错误时,VBA会进行提示,让用户对有误的代码做修正。现例举四个编译错误,其中红色代码表示出错的语句。
(1)按下快捷键【Alt+F11】进入VBE窗口,单击菜单【插入】\【模块】,然后在模块代码窗口中输入以下代码“Sub 11()”,当敲下回车键后,VBA会弹出一个错误提示,见图所示。该错误是使用了不正确的标示符做为宏名称引起的。宏名称不以数字开头。
(2)在模块代码窗口中输入代码“Sub 我的宏()”,然后回车,程序会自动将Sub程序之外壳补充完整,成为:
Sub 我的宏()
End Sub
然后在中间录入以下语句:
IF [a1]=10
当敲下回车键后,立即弹出图所示错误提示,它表示IF语句未录入完整。完整的代码应是IF…Then…不是仅仅有IF忽略Then。
图 错误提示一 图 错误提示二
(3)将前面代码中的修改“if [a1]=10”修改为“Range(“a1”)=10”,当敲下回车键后立即弹出图所示的错误提示。该提示表示代码中使用了无效字符,其中无效字符指的全角双引号,Range的参数只能使用半角双引号。
(4)将前面代码中的修改“Range(“a1”)=10”修改为“Mid([a1])=1”,当敲下回车键后立即弹出图所示的错误提示。该提示表示Mid函数的参数不全,也就是缺少逗号分隔符“,”。在[a1]后面加两个逗号与参数就可以消除错误。
图 错误提示四 图 错误提示四
2.运行时错误
运行时错误是指代码编写时无法判断,在执行程序时才出现的错误。下面也举四例运行时错误。
(1)在模块代码窗口录入以下代码,表示获取第11个工作表的表名:
Sub 取表名()
MsgBox Sheets(11).Name
End Sub
如果当前工作簿中工作表数量不足11个,那么按下【F5】键执行程序时,将产生图所示的错误提示。其中下标越界表示引用了不存在的对象,工作表仅三个时引用第11个就会越界。
图 运行时错误1
(2)在模块代码窗口录入以下代码,表示对Z2048576单元格填充红色背景。
Sub 填充红色()
Range("Z2048576"). = 3
End Sub
录完代码后,按下【F5】键执行程序,立即弹出图所示错误提示。因为Excel 2007最多1048576行,不存在Z2048576这个单元格,所以对该单元格进行任何操作都会失败。
图 运行时错误2
(3)在模块代码窗口录入以下代码,用于将当前表数据区域的字体大小修改420磅。
Sub 设置字体大小()
= 420
End Sub
录完代码后,按下【F5】键执行程序,立即弹出图所示错误提示。因为字体只支持1到409之间,而代码中420已经超过Excel的允许范围,所以报错。
图 运行时错误3
(4)在模块代码窗口录入以下代码,用于统计A列数字个数。
Sub 计算A列数字个数()
Dim i As Byte
i = (Range("A:A"))
MsgBox "A列数字个数为:" & i
End Sub
录完代码后,按下【F5】键执行程序,如果A列有1000个数字,那么会立即弹出图所示错误提示。代码为变量i为byte类型,那么它只能表示0到255之间的数值,当A列数字个数超过255个时就会产生错误提示。
图 运行时错误4
根据以上分析,Excel VBA有着完善的错误捕捉功能,用户的代码不管在运行前还是运行后产生错误,都可能通过错误提示帮助用户获取错误信息。不仅知道代码中存在错误,而且可以明确地看到错在哪一句代码。例如“计算A列数字个数”这段代码中,当产生错误提示框时,单击“调试”按钮,程序会将出错的代码行黄色显示,且在其左方用箭头标示,见图所示。
图 标示错误语句
以上错误捕捉功能需要设置好的前提下才可能发挥作用。在下一章中,将重点介绍VBE环境及其选项设置。
第三章
巧设VBA编辑器提升编程效率
VBA是Visual Basic for Application的简称,表示一种程序语言。VBE是Visual Basic Edirtor的简称,它是VBA的容器,用于存放VBA代码。设置好VBE对VBA代码的编写、使用有交大的帮助。
本章要点:
认识VBE组件
VBE中选项设置
认识VBE组件
VBE窗口中包括很多组件,包括菜单栏、工具栏、代码窗口、立即窗口等等,有效地利用这些组件可以对开发插件、录入代码、检测错误有着举足轻重的作用。
访问VBA开发环境
从本节开始,正式进入代码编写环节。在编写代码前,有必要认识一下代码的容器:VBE。
进入VBE的方法有三种:功能区按钮法、快捷键法和右键菜单法
1.功能区按钮法
Excel 2003中可以通过菜单【工具】\【宏】\【Visual Basic编辑器】来进入VBE界面,而Excel 2007在默认状态下隐藏了该菜单。在2007中需要设置Excel选项、调出【开发工具】功能区。具体步骤如下:
(1)单击菜单【Office按钮】\【Excel选项】打开选项对话框;
(2)在对话框中将“在功能区显示“开发工具”选项卡”打勾,单击“确定”按钮后,在功能区即可出现【开发工具】功能区。见图所示:
图 调出开发工具功能区
(3)单击功能区中的“Visual Basic”按钮,VBA编辑器立即呈现出来。
2.快捷键法
调出VBA编辑器的快捷键是【Alt+F11】。而在VBA编辑器中再次使用快捷键【Alt+F11】则可以缩小VBA编辑器的窗口并返回到Excel工作簿窗口。
如果需要关掉VBA编辑器再返回Excel工作簿窗口,则可以使用快捷键【Atl+Q】。
3.右键菜单法
在工作表标签中单击右键,弹出的菜单中将出现【查看代码】菜单,见图所示。单击该子菜单即可以入VBA编辑器。
右键菜单法进入VBA编辑器与前面两法稍有分别,它进入VBA编辑器后会定位于当前工作表的窗口。例如选择“月报表”工作表后再单击右键,从【查看代码】菜单进入VBA编辑器界面后,会自动选定“月报表”工作表,右边也相应显示“月报表”的代码窗口,见图所示。
图 从右键菜单进入VBE 图 定位于“月报表”
认识VBE的组件
VBA有很多组件:菜单栏、工具栏、工程资源管理器、属性窗口、代码窗口、对象与过程窗口、立即窗口、本地窗口、监视窗口、对象窗口以及工具箱。不同组件有不同作用,不过菜单与工具栏会出现重复功能。
VBE中的所有组件无法同时出现,某些组件与组件之间是排斥关系。如组件A出现,那么组件B就会关闭或者隐藏,反之亦然。在图中罗列了大部分组件。
1.菜单栏
VBE中的菜单栏包含了VBE中大部分功能。单击二级、三级单菜单可以执行文件导出、导入、查找、删除、新建组件、设置格式、定义选项、加密码、代码调试、窗口切换、调用帮助等等功能。
另外还有一种快捷菜单,即右键菜单。右键在不同地方将产生不同菜单,例如代码窗口和在窗体中、立即窗口所产生的右键菜单就大大不同。即使同一代码窗口,在不同的状态下,仍然也会有不同的菜单,例如正常状态和中断状态下,代码窗口的右键菜单也不一样的。
菜单栏和所有右键菜单可以利用VBA来定制,包括创建新菜单,删除、隐藏原有菜单等等。在本书第29章将讲述VBE中定制菜单和工具栏。
图 VBE部分组件
2.工具栏
工具栏包含功能在菜单中都有,不过工具栏的按钮在操作上比菜单栏方便、直观,所以工具栏在工作中使用率会高于菜单栏。
工具栏同样可以定制,创建或者删除工具栏都可以,在本书第28章将会讲述。
工具栏中的按钮可以通过功能提示来查看名称及了解功能。只要将鼠标指针移向任何一个按钮,屏幕上将出现它的名称和快捷键(如果有的话)。图即为“复制”按钮的功能提示和快捷键提示。
工具栏中包括四条组成,默状态下可能仅显示“标准”工具栏。如果需要调出其它工具栏,可以在工具栏或者菜单栏中单击右键,在需要显示的工具栏名称上单击使其打勾即可,见图所示。
图 工具栏的屏幕提示 图 调出“编辑”工具栏
3.工程资源管理器
工程资源管理器用于管理VBA工程及其对象。一个工作簿有一个工程,默认名称为“VBAProject”;每个工程有多个对象,包括工作表、窗体、模块等等。在工程资源管理器中可以管理无数个工程,如图所示:
如果进入VBE界面后未显示工程资源管理器,可以使用快捷键【Ctrl+R】调出。
工程资源管理器是以目录树形显示当前的所有工程,以及每个工程中的子对象。如果需要查看或者编辑“Sheet1”中的代码,进入工程资源管理器中双击“Sheet1”,右边马上会显示“Sheet1”中所有代码;如果需在查看或者修改用户窗体“UserForm1”中的代码,则需要在“UserForm1”上单击右键,从弹出的快捷键单中选择“查看代码”,若双击窗则只能查看窗体本身。
图 工程资源管理器 图 查看窗体中的代码
工程资源管理器中默状态下有一个工程,代表当前工作簿,以及与工作表数量对应的Sheet1、Sheet2、Sheet3和Thisworkbook对象。模块、用户窗体(UserForm)和类模块等等需要手工添加才会出现。
4.属性窗口
属性窗口用于设置、修改各种对象的属性。对象包括工程对象、窗体对象、模块对象及窗体中的图形、组合框、标签等等对象。而属性则包含很多很多项目,例如字体大小、颜色、边距、高度、标题、显示方式等等。而且不同的对象有各自己独特的属性。
现以设置标签“Lable1”的名称、字体与对齐方式为例,演示属性窗口的具体用法。
(1)在VBE界面中,单击菜单【插入】\【用户窗体】。此时在右边产出现一个新建窗体,同时出现“工具箱”,见图所示。如果未出现“工具箱”,可以单击菜单【视图】\【工具箱】。
(2)左键按下工具箱中的图标“A”即标签工具,然后在窗体中按下向右下方拖动,从而产生一个新标签,其默认名称为“Label1”;
(3)在“Label1”呈选择状态下按下快捷键【F4】调出属性窗口,见图所示;
图 新建窗体 图 显示标签的属性
(4)进入属性窗口,找到“Caption”属性,在右边录入“请输入用户名:”
(5)找到“字体”属性,单击右边的图标,将弹出一个字体对话框。从对话框中分别选择“黑体”、“粗体”、“三号”,然后单击“确定”返回;
(6)找到“TextAlign”属性,单击其右边的列表框,将弹出三个选项,表示三种对齐方式。见图所示。其中第二项表示居然对齐,选择第二项;
(7)因字体加大,标签显示不完整,手工调整到适合大小后,效果见图所示:
图 设置标签文字的对齐方式 图 设置属性的标签效果
其它属性也可以用类似方法设置。即在对应的属性右边的文字框中录入字符,或者在弹出的对话框中设置选项,以及从下拉列表中选择需要的选项。用户举一反三可以学会所有属性的设置。
5.代码窗口
代码窗口是用于存放VBA代码的处所,它是VBE中最核心的组件。代码窗口包括工作表代码窗口、工作簿代码窗口、窗体代码窗口、模块代码窗口和类模块代码窗口。
6.对象与过程窗口
对象与过程窗口是指位于代码窗口上方的对象列表和窗过程列。见图所示。
图 对象与过程列表
图中左上角的下拉列表是对象列表,单击下拉箭头可以罗列出所有可用的对象名称;右边的下拉列表是可用的过程列表,单击下拉箭头可以罗列出所有可用的过程名称。
这两个列表对用于辅助代码录入,以及提示当前对象所支持的对事件。用户也可以永远不用它,采用手输入代码。但是在输入工作表事件或者工作簿事件时,通过对象与过程下拉列表自动产生代码比手工输入的效率更高,且更准确。关于它们的用法在后面关于事件的章节会详细的说明。
7.立即窗口
立即窗口有两个功能:显示调试代码时产生的结果(信息),以及执行单句的代码。
立即窗口默认是隐藏状态,可以使用快捷键【Ctrl+G】将其调出。
现分别演示立即窗口的两种功能与操作步骤:
(1)在开启任意工作簿后按下快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【模块】;
(3)按下快捷键【Ctrl+G】显示立即窗口;
(4)在模块中输入以下代码:
Sub 显示当前工作簿全名()
IF Len() = 0 Then
"当前工作簿未保存"
Else
End IF
End Sub
(5)将光标定位于代码中任意位置,单击快捷键【F5】执行代码,在立即窗口将会显示代码执行结果。如果当前工作簿未保存,则立即窗口显示“当前工作簿未保存”,则否显示工作簿全名,包括其路径。见图所示;
(6)清除立即窗口中的字符,然后录入以下代码:
(xlWBATChart)
然后单击回车键,注意必须是光标位于当前代码行最右边时单回车,此时可以发现Excel新建了包含一个工图表的工作簿。
(7)在第二行输入以下代码然后回车,则当前工作簿中立即新建10个工作表。
Count:=10
执行代码后,工作簿中将有13个工作表,见图所示。
图 在立即窗口显示信息 图 在立即窗口执行单行程序
8.工具箱
工具箱对于VBA程序开发是非常重要的工具。默认状态工具箱包括了15种工具,用户可以利用这些工具设置出和其它任何软件程序类似的界面。如果默认工具不够用,还可以右键定义新的工具。
调用工具箱的方式和前面的任何组件的方式都不同,它是建立在UserForm的基础上的。当用户选择UserForm对象时才出现,其它状态下一律隐藏。
显示工具箱的方法是单击菜单【插入】\【用户窗体】,此时工具箱将自动显示出来。工具箱的外观见图所示。如果已经有窗体,不想再建立窗体,则可以双击窗体名(默认为UserForm1,根据实际情况,用户可能修改为其它名称),然后单击菜单【视图】\【工具箱】即可。
工具箱是可以定制的,包括新建页、附件控件等等。步骤如下:
(1)在窗体的右上角空白区单击右键,从菜单中选择【新建页】即可建立一个名为“新页”的页面;
(2)在“新页”二字上面单击右键,从菜单中选择【重命名】,并录入名称“我的新工具”;
(3)新建的页是完全空白的,可以对其任意添加新的组件。在当前页中间空白区单击右键,从菜单中选择【附件控件】,弹出“附件控件”对话框;
(4)在对话框中将需要的组件打勾,然后返回工具箱。工具箱的定制效果见所示:
图 工具箱外观 图 定制工具箱
VBE中不同代码窗口的作用
前一小节中谈过VBE界面中的代码窗口用于存放VBA代码,但是了解这一点还远远不够。在VBE中有五类代码窗口,代码在不同窗口中将产生不同作用,哪怕代码完全一致。
1.工作表代码窗口
工作表代码窗口用于存放工作表事件代码,该代码仅仅在当前表中调用。普通Sub过程保存在工作表事件代码中虽然也可以执行,而且其它模块或者工作表也可以调用,但却有诸多不便。所以正常情况下,大家都达成一个共识:工作表事件代码存放工作表代码窗口,Function过程和Sub过程保存在模块中。
工作表代码窗口的开启方式为:使用快捷键【Ctrl+R】调出工程资源管理器,然后双击工作表名称,右边立即出现工作表事件代码窗口。
每个工作表都有自己的代码窗口,在窗口中储存自己的事件相关的代码,该代码只有在当前表才可以调用。例如在“生产表”的代码窗口录入以下报告选区地址的代码,见图所示:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
MsgBox
End Sub
图 在“生产表”代码窗口录入SelectionChange事件代码
使用快捷键【Alt+Q】返回Excel工作表,在“生产表”工作表中选择任意区域,立即弹出当前选区地址的信息,见图所示。如果选择多个区域,同样提示多区域的地址,中间用逗号分隔,见图所示。而在其它任何工作表选择区域则没有任何反应。
如果一定要在其它工作表调用当前工作表事件的代码,也可以采用以下步骤完成:
(1)将“生产表”中代码前的Private删除;
(2)在“Sheet2”的代码窗口录入以下代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call Sheets("生产表").Worksheet_SelectionChange(Target)
End Sub
其中call表示调用其它过程。按下【Alt+F11】返回工作表,进入Sheet2工作表中,选择任意区域也会同样弹出选区地址信息。
图 提示选区地址 图 提示多区域地址
注意:工作表事件的代码中Private表示将当前Sub程序声明为私有,即只有当前工作表模块或者工作表才可以调用。本例中为了让Sheet2中可以调用,必须去除Private。对于Private的更多知识,参阅本书第五章。
2.工作簿代码窗口
工作簿代码窗口的名字为ThisWorkbook,该窗口用于存放工作簿级别的事件代码。虽然它也可以存放Function过程和普通的Sub过程,但根据习惯,以及使用上的方便性,该窗口仅仅存放工作簿事件相关代码。
例如图是工作簿级别的事件代码,表示不管任何时候关闭工作簿都保存一次。该代码仅仅在关闭工作簿时执行,其它任何窗口无法调用该事件代码。
图 工作簿及关闭事件的代码
3.窗体代码窗口
窗体代码窗口用于存放窗体、控件相关的代码。它的代码只能在窗体中使用,其它任何窗口无法执行。
查看窗体中代码的方法是在工程资源管理器中的窗体上单击右键,从菜单中选择【查看代码】。例如图中的代码位于UserForm1的代码窗口,表示启动窗体时设置它的左边距为100。在UserForm1以外的任何窗口以任何方式都无法调用此代码。
图 窗体代码窗口
4.模块代码
工作中使用最多的就是模块代码窗口。在模块代码窗口中存放Sub过程和Function过程。这些过程可以在当前模块执行,也可以供其它任何窗口调用。工作表事件、工作簿事件、窗体事件和类模块都可以使用模块中的程序,而模块与模块之间也可以相互调用。
5.类模块
类模块是用户自定义类的属性和方法的模块。单击菜单【插入】\【类模块】即可创建一个类模块,其图标为。
类模块的代码通常用于应用程序级别的事件,在应用程序对应的事件中调用该代码。在本书第十五章将详述类模块的用法。
VBE中选项设置
VBE中的选项设置对于VBA爱好者来说至关重要,如果该选项设置不当,会对编程带来无限烦恼。例如无法捕捉错误、没有函数提示、控件无法对齐等等。
打开VBA编辑器选项的方法是单击菜单【工具】\【选项】。选项对话框外观见图所示。
本节对选项对话框中所有组件做详细讲解,并建议如何优化设置。
图 VBE选项
编辑器选项
选项对话框中第一个选项卡即可“编辑器”。该选项卡中各项目功能介绍如下:
1.自动语法检测
自动语速法检测是指编写代码时自动对每句代码检查是否有错误,如果有错误则弹出警告框,同时将错误的语句红色显示,提示用户代码有误。
例如下图中,在输入For语句时忘记了In,使代码产生错误。当录入该句代码并回车时,程序会立即弹出一个编译错误的提示,告知错误类型,同时红色标示错误语句。
图 自动语法检测
建议勾选此选项,以协助自己了解当前代码中的错误类型,从而快速修正。
2.要求声明变量
该选项表示强制用户在编写代码时声明所有变量,否则程序无法执行。其具体表现为在任何新建模块、工作表代码窗口和工作簿代码窗口都产生“Option Explicit”语句。
强制声明变量的优点有三个:
提升代码运行效率
防止因变量类型错误带的错误
在输入对象变量时可以自动列出快速信息
从图中可以看到,变量rng未声明,所以运行代码时会产生一个编译错误,提示变量未定义。
图 提示变量未定义错误
建议勾选该选项。
3.自动列出成员
该选项可以在录入代码时自动产生语法提示,包括类型与对象的属性、方法等等。
例如在输入“dim rng as ”语句时,VBA会列出所有可用的变量类型供用户选择,而不需要手工录入,从而防止输入错误的变量类型,见图所示和所示:
图 自动列变量名称 图 自动列表对象的属性与方式
如果不勾选该选项,则无法弹出提示,只能手工录入。建议勾选。
4.自动显示快速信息
该选项表示在录入代码时对参数进行提示,方便用户核查录入的参数是否正确。
该提示主要体现在三方面:
参数个数
参数类型
当前参数
从图的快速信息中可以看出,MID函数有三个参数,第一参数是String型,第二参数是Long型,第三参数是可选参数,它代表长度……这些信息都为编程提供了便利。
而图的快速信息可以看出,Range有两个参数,第二个参数是可选参数。当前正在录入第二个参数,因为第二参数已加粗显示。
图 提示MID的参数信息 图 提示当前参数
建议勾选该选项。
5.自动显示数据提示
该选项是指中断模式下,设定一个断点,则当执行代码并且鼠标指针指到变量上面时,数据提示窗口就会显示出变量的值。具体操作步骤如下:
(1)在模块中输入以下代码:
Sub Test()
Dim temp As Byte
temp = 100
MsgBox temp
End Sub
(2)在第四句代码在前面单击一次,表示将该句设置为断点。也可以光标定位于该句代码,然后按下F9来设置断点;
(3)按下F8进入调试语句状态,它后逐句执行代码。在执行代码时将鼠标指针指向Msgbox后面的变量temp上,查看提示信息;
(4)在多次按下F8后,该提示会产生变化。因为变量temp在初期值为0,而在“temp=100”语句执行后就变成了100,所以提示信息也会相应的变化。
图 数据提示1 图 数据提示2
本选项用途不是很广,用户可以勾选也可以不勾选。
6.编辑时可拖动文本
该选择表示用鼠标可以拖动代码,等同于剪切、粘贴之功能。
图中演示了从test1程序中将其代码拖到test2程序中的方法。具体步骤为:
(1)选择需要拖动的代码;
(2)按下左键不放,鼠标下会呈现一个虚框,表示当前可以拖动代码;
(3)拖到目标位置后松开鼠标。如图,当前的插入点是过程test2的第一行,当松开鼠标后,代码就会插到入该位置。类似于剪切、粘贴。最后效果见图所示。
图 拖动代码 图 拖动后的效果展示
建议勾选该选项,以方面代码移动。
7.缺省查看所有模块
该功能是在模块代码中显示所有过程的代码。当模块中有多个过程时,该功能极其有用,减少切换时间。
如果不勾选该选项,窗口中仅仅显示当前过程或者声明部分的代码。如果需在查看另一个过程代码需要从过程下拉列中切换。见图所示。
建议勾选该选项,使代码阅读更方便。
8.过程分隔符
该选项表示利用一条横线将多个过程的代码或者变量声部分开。
图是不分隔代码时的外观,可以发现它的缺点是不利于代码查看。与图相比不够分明。
图 切换显示不同过程 图 未分隔所有过程及声明
建议勾选该选项,使代码阅读更方便。
9.自动缩进
该选项表示定位代码的第一行后,所有接下来的代码会在该定位点开始。更通俗地说法即为统一设定左边距。
如果勾选该选项,那么第一行左边距为4时,第二行默认状态也是4,否则默认为0.
建议勾选该选项,使代码更美观,也方面阅读。
编辑器格式选项
编辑器格式选项卡中包括了所有与代码显示方式相关的选项。它可以决定不同代码如标准文本、断点、标签等等以什么字体、颜色、大小显示来出,使用户方便区分。
在默认状态下,VBA的设置已经非常合理,虽然用户可以随意定制,但建议不要做任何修改。
图即编辑格式选项卡的界面,而图是默认状态下的执行点、标签和断点样式。
图 编辑器选项卡 图 默认状态下的代码格式
通用选项
通用选项的项目较多,其中最重的几个项目包显示网格、错误捕捉和显示工具提示。
通用选项的外观
1.显示网格
该选项是针对用户窗体的,即窗体在设计状态下,显示网格线,而窗体中的控件也以网格为基准对齐。它的优点是窗体内有多个控件时可以轻松地对齐。
图是通用选项卡的外观,图是勾选“显示窗线”和“对齐控件到网格”状态下的窗体。
图 通过选项卡 图 让设计状态的窗体显示网格
对于“对齐控件到网格”可根据需要来设置。当需要多控件对齐时可勾选该选项;在对控件微移时则不能勾选。比对某控件需要向右微调4个单位,另一个控件需要向左微调3个单位,那么对齐控件到网格后就无法完成。
2.显示工具提示
该选项表示工具栏的所有按钮。如果勾选该选项,鼠标指向按钮时可以显示它的功能与快捷键提示,这对于学习VBA有较大的帮助。
3.错误捕捉
错误捕捉包括“发生错误时则中段”、“在类模块中中断”和“遇到未处理的错误时中断”三个选项。各选项含义见表3-1所示。
表3-1 错误捕捉含义
类型
含义
发生错误则中断
任何错误都会使工程切换到中断模式,不管错误处理程序是否为活动的,也不管代码是否在类模块中。
在类模块中中断
任何在对象类模块中失去句柄的错误会让工程进入中断模式。
在失去句柄错误时中断
任何其他失去句柄的错误会让工程进入中断模式。
其中第一项和第三项可以演示一下差异。
(1)清空当前表A1:B1,在模块中录入以下代码:
Sub 错误捕捉()
On Error Resume Next
Dim i As Byte
i = [a1] / [b1]
IF <> 0 Then GoTo err:
MsgBox i
Exit Sub
err:
MsgBox "出错了"
End Sub
(2)将错误捕捉设为第一项;
(3)光标定位于代码中任意位置,按下快捷键【F5】执行代码,程序立即弹出运行时错误对话框;
(4)从对话框中单击“调试”按钮,“i = [a1] / [b1]”语句呈黄色显示,表示该语句在错误;
(5)将错误捕捉设置为第三项后再执行代码,程序不再弹出错误提示。
调试代码或者初学时,应该将选项设置为第一项;代码编写完成正式执行时则需要设置为第三项。
VBA代码保护
VBA代码是需要保护的,基于两种目的:保护成果、防止无意中破坏。
保护VBA代码有两种方式:共享工作簿、代码加密。
1.共享法
共享法是指对工作簿共享,从而实现不可查看代码的目的。听起来像悖论,按下面的步骤操作却一定可以达成需求。
(1)在工VBA模块中录入代码后按下快捷键【Ctrl+S】保存工程,必须是xls格式或者xlsm、xlam、xla格式;
(2)返回工作表界面,进入【审阅】功能区,单击【共享工作簿】按钮,将“允许多用户同时编辑”打勾,然后保存;
(3)重新打开该工作簿,进入VBE界面后双击当前工作簿工程,将弹出所示的提示:
图 共享工作簿 图 保护后状态
2.加密法
VBA的选项对话框提供了代码加密的功能,步骤如下:
(1)在VBE界面下单击菜单【工具】\【VBAProject属性】,每个工程的默认名称是“VBAProject”,但该名称可以手工修改。如果曾经修改过,以实际名称为准;
(2)在“工程属性”对话框中,切换到“保护”选项卡,选择“查看时锁定工程”,并在下面的两个文字框录入两次相同的密码;
(3)保护工作簿后重启该文件,使用【Alt+F11】进入VBE界面,双击工程名称时将弹出图所示对话框,如果未录入正确密码将禁止查看。
图 设置工程密码 图 开启已保护的工程时确认权限
上面的两种方法也可以同时使用。
注意:不管如何设置,VBA的保护措施是很脆弱的,不需要很专业的程序员就可以攻破它。所以如果对自己的代码安全性要求较高,可以使用COM加载宏,而不是Excel VBA编写。在本书第32章将讲述用VB开发COM加载项的具体方法。
进阶篇:VBA语法、过程与事件
第四章
VBA基本概念
VBA语言是面向对象的一种程序语言。在VBA中有很多很多对象,每个对象涉及自己范畴的很多属性和方法,还拥有各自专属的事件。在本章中将详细介绍以上VBA的基础知识。
本章要点:
理解VBA的对象、属性与方法
认识VBA的事件
VBA的运算符
简单的字符处理函数
理解VBA的对象、属性与方法
几乎90%以上的VBA程序都是在操作对象,利用对象的方法来读取或者写入对象属性……在编写代码前必须对Excel的对象有全面的认识。
什么是对象
一个最简单的故事都一定有人物、事件、或者时间、地点。人物是故事的核心,那么VBA也相应的有对象、属性、方法和事件,其中对象是VBA的核心。
很多很多软件都带有VBA环境,那么VBA在不同软件中的操作对象也是不同的。如WORD中的Application对象是Word,Excel中的Application对象则是Excel。
VBA对对象的操作语句格式总是遵循这样的格式:“对象.属性”、“对象.方法”或者“父对象.子对象.属性”……例如在下面的实例:
Sheets("工作表").Name——Sheets("工作表")是对象,Name是对象的属性
Workbooks(2).Close——Workbooks(2)是对象,Close是对象的方法
Range("a1:a100").——Range("a1:a100")是父对象,Comment是子对象,Delete是方法
Excel有数百个对象,表4-1是常见对象名称其及含义。
表4-1 常见对象及其含义
对象名
含义
Application
代表整个 Excel 应用程序。
Window
代表窗口
Worksheet
代表一个工作表
Sheets
指定的或活动工作簿中所有工作表的集合
ShapeRange
代表形状区域,它是文档中的一组形状
PivotTable
代表工作表上的数据透视表
Workbook
代表一个 Excel 工作簿
Shape
代表绘图层中的对象,例如自选图形、任意多边形、OLE 对象或图片
Range
代表某一单元格、某一行、某一列、某一选定区域,或者某一三维区域
Name
代表单元格区域的定义名。名称可以是内置名称(如Print_Area)或自定义名称
Chart
代表工作簿中的图表
FileDialog
提供文件对话框,其功能与 Office 应用程序中标准的“打开”和“保存”对话框类似
CommandBarPopup
代表命令栏上的一个弹出式控件
CommandBar
代表容器应用程序中的一个命令栏
如何理解属性
属性是一个对象的外部和内部特征,包括大小、颜色或边距、数量,或者某一方面的行为,例如对象是否可以激活、是否可见的、是否可以刷新等等。可以通过修改对象的属性值来改变对象的特性。
可以打一个比方,桌子是一个对象,桌面是方形的、有四只脚、由木头组成、可以拆解等等就是属性。而拆解这个动作则属于方法。
一个工作表具有哪些属性?可以用下列两种方法获取。
1.自动成员列表
任何对象都有属性,而且在录入代码时可以从自动成员列表中看到其属性。例如图中,输入“worksheets.”后将会看到一个下拉列表,在该列表中包含了工作表的属性及方法,其中带有手形图标的是属性,另一种是方法。
然而很多很多对象还有一些隐藏属性,在该列表中没有罗列出来。可以按下列步骤调用其隐藏属性。
(1)按下快捷键【F2】打开对象浏览器;
(2)在空折区单击右键,从菜单中选择【显示隐含成员】,见图.
(3)再返回模块代码窗口,输入“worksheets.”后将会看到一个新的下拉列表,里面有灰色的隐藏属性。
图 工作表属性列表 图 从对象浏览器调整显示隐藏属性
然而,这种方式仍然有局限制,即下拉列表即有属性又有方法,不利于查看。所以方法2可以有针对性的查看属性。
2.查看帮助
在VBA的帮助中有着完善的对象、属性、方法查询系统。仍然Worksheets 对象为例,需要查看其属性的步骤如下:
(1)在VBE窗口中按下快捷键【F1】调出帮助窗口;
(2)在搜索栏中输入“Worksheets 对象成员”并回车;
(3)查找结果中包含了100项关于Worksheets 对象成员的信息,见图所示。单击第一项“Worksheets 对象成员”即可查看到Worksheets对象的成有属性和方法属性和方法是分开罗列出来的,见图所示。
图 查找Worksheets 对象成员 图 帮助中的Worksheets属性列表
如何理解方法
方法指的是对象能执行的动作。它是一个动词,而对象是一个名词。但是初学者需要记住,它的语法与汉语语法是不同的。
“创建工作表”:创建是动词,表示方法,“工作表”是名词,代表对象。
在VBA中却需要对象在前,方法在后,示例如下:
:worksheets是工作表对象,add是方法,表示新建。
和小节中的办法一致,在帮助中可以到关于worksheets的所有方法,见表4-2所示:
表4-2 worksheets方法一览
名称
说明
Add
新建工作表、图表或宏表。新建的工作表将成为活动工作表
Copy
将工作表复制到工作簿的另一位置
Delete
删除对象
FillAcrossSheets
将单元格区域复制到集合中所有其他工作表的同一位置
Move
将工作表移到工作簿中的其他位置
PrintOut
打印对象
PrintPreview
按对象打印后的外观效果显示对象的预览
Select
选择对象
判断对象的属性与方法
从图中可以看出,每个对象的属性和方法都同时出现在成员列表中。那么如何区分哪个项目是属性,哪一个是方法呢?主要要三种办法:
1.根据自动成员列表中的图标判断
成员列表中带绿色图标是方法,另一种图标即为属性。
2.看帮助
在VBA的帮助中有完善的解说,从中可以看到其性质及功能详解。只是每一个列表中的成员都逐一查看效率不高。
3.判断词性
方法是动词,它有一个动作,每个动作会产生一个可见的结果。例如:
:Add动作的结果是产生一个新工作表
:Select动作的结果是选择当前工作簿所有工作表
:PrintPreview动作的结果是当前表进入预览状态
而属性是名词,表示一种状态或者特征,可以将对象的这个状态显示在单元格中。例如:
[a1] = :Count表示工作表的数量,执行本语句可以在A1单元格返回一个值
[a1] = (1).Name:Item是工作表集合的属性,代表Worksheets的子集,但Item本身却是一个对象集合,在本例中,Item(1)代码第一个工作表
[a1] = Worksheets(1).Visible:Visible表示工作表的可见性属性,可以读取,也可以改变这一属性。如Worksheets(1).Visible=False即可将第一个工作表隐藏起来
认识VBA的事件
所有对象都有属性与方法,同时也具有事件。充分地利用事件可以使程序实现自动化。
什么是事件
事件是对象在某个状态下引发的动作。每个对象都会有很多的事件,不同的事件有不同的触发条件。
如果更形象地阐述,路人甲不小心踩了路人乙,路人乙则骂他“没长眼?”。在这个过程中,被踩是一个事件。路人甲踩了路人乙则引发路人乙“被踩”的事件,而路人乙回骂则是事件所触发的动作。同一个事件可以触发多个动作,例如路人乙骂完之后再踢回一脚等等。
而Excel的事件也是类似的情形。例如以下代码:
Private Sub Workbook_Open()
Sheets(1).Select
[a1]=date
End Sub
此代码是一个工作簿开启事件,由开启事件引发了两个动作:进入第一个工作表和在A1单元格显示当前日期。
事件的分类及其用途
VBA有很多类事件,分类的标准由对象来决定。表4-3是对象其及事件的对应关系表。其它分类方式在后面的章节将进行介绍。
表4-3 事件分类
对象
事件
Application
应用程序事件
Workbook
工作簿事件
Worksheet
工作表事件
Chart
图表事件
UserForm
窗体事件
Label
标签事件(窗体中的控件)
Image
图像事件(ActiveX控件)
表中并没有罗列完所有事件。因为每一个控件都有自己的事件,而VBA可以调用的对象有超过100种。
虽然具有事件的对象很多,不过常用的是工作簿事件、工作表事件及窗体事件。
事件在工作中非常有用,常用于实现过程的自动化。例如工作簿打开时自动执行某程序,工作表切换也自动执行一个程序,鼠标移动窗体时……通过这些事件的运用,可以在某个条件全自动执行数据计算或者环境设置、变量赋值等等,从而减少手工执行代码,提升工作效率。
对于事件的具体用法和实例,在本书第8章中将再详述,本章仅仅了解基本概念。
VBA的运算符
VBA最主要的两项任务是计算和界面设计。在进入程序运算前需要了解VBA中有哪些运算符,以及其运算规则。
VBA中运算符的分类
VBA中有很多运算符,大致可以分为四类,见下表。
表4-4 运算符分类
种类
功能
算术运算符
用来进行数学计算的运算符
比较运算符
用来进行比较的运算符
连接运算符
用来合并字符串的运算符
逻辑运算符
用来执行逻辑运算的运算符
算术运算符
算术运算符包括7个,其符号及功能见下表:
表4-5 算术运算符列表
运算符
功能
^
求一个数字的某次方,如 A^B
*
乘法运算
/
除法运算
\
对两个数作除法并返回一个整数
Mod
求两数的余数
+
加法运算
-
减法运算
比较运算符
比较运算符包括6个,其符号及功能见下表:
表4-6 比较运算符列表
符号
功能
<
小于
<=
小于或等于
>
大于
>=
大于或等于
=
等于
<>
不等于
逻辑运算符
逻辑运算符包括6个,其符号及功能见下表:
表4-7 逻辑运算符列表
符号
功能
And
用来对两个表达式进行逻辑连接
Eqv
用来对两个表达式进行逻辑等价运算
Imp
用来对两个表达式进行逻辑蕴涵运算
Not
用来对表达式进行逻辑否定运算
Or
用来对两个表达式进行逻辑析取运算
Xor
用来对两个表达式进行逻辑互斥或运算
最常用的是And、Not、Or三种运算
运算符
And运算符的运算规律见表4-8所示:
表4-8 And运算符运算规则
如果条件一为
且条件二为
则结果为
TRUE
TRUE
TRUE
TRUE
FALSE
FALSE
TRUE
Null
Null
FALSE
TRUE
FALSE
FALSE
FALSE
FALSE
FALSE
Null
FALSE
Null
TRUE
Null
Null
FALSE
FALSE
Null
Null
Null
实例:
Sub AND运算()
Dim A, B, C
A = 20
B = 15
C = 12
MsgBox (A > B And B > C) ' 返回 TRUE
End Sub
运算的运算符
Or运算符的运算符规则见表4-9所示:
表4-9 Or运算符运算规则
如果条件一为
且条件二为
则结果为
TRUE
TRUE
TRUE
TRUE
FALSE
TRUE
TRUE
Null
TRUE
FALSE
TRUE
TRUE
FALSE
FALSE
FALSE
FALSE
Null
Null
Null
TRUE
TRUE
Null
FALSE
Null
Null
Null
Null
实例:
Sub OR运算()
Dim A, B
A = 10: B = 1
MsgBox (A > 8 Or B > 8) '有一个条件满足,返回TRUE
MsgBox (A > 100 Or A < 10) ' 两个条件都不满足,返回FALSE
End Sub]
代码中半角单引号后面的汉字说明是程序的注释,程序执行时会自动忽略它。注释仅仅对代码的含义做说明,完全不影响代码的功能。对于注释的详细说明请参见本书第9章。
运算符
Not运算符的规则见表4-10所示
表4-10 Not运算规则
如果 条件 为
则 结果 为
TRUE
FALSE
FALSE
TRUE
Null
Null
实例:
Sub Or运算()
IF Not ([A1]) Then
MsgBox "A1不是数字"
Else
MsgBox "A1是数字或者为空"
End IF
End Sub
运算符的优先顺序
当一个表达式涉及到多个运算符时,就必须考虑运算符的优先顺序。在一个表达式中进行运算时,每一部分都会按预先确定的顺序进行计算求解,称这个顺序为运算符的优先顺序。
在表达式中,当运算符不止一种时,要先处理算术运算符,接着处理比较运算符,然后再处理逻辑运算符。所有比较运算符的优先顺序都相同以出现位置为基准;也就是说,要按它们出现的顺序从左到右进行计算。而算术运算符和逻辑运算符则必须按下列优先顺序(由上至下)进行处理。
可以用下表来表示三类运算符的优先级:
表4-11 运算符的优先级
算术动算符
比较动算符
逻辑运算符
指数运算:^
相等:=
Not
负数:-
不等:<>
And
乘除法:*、/
小于:<
Or
整除法:\
大于:>
Xor
求模运算:Mod
小于等于:<=
Eqv
加减法:+、-
大于等于:>=
Imp
字符连接:&
Like和Is
用下例的一个实例,可能读者会更易明白运算优先级在VBA中的作用:
Sub 运算优先级测试()
MsgBox IIF(Not ([a1]), [a1] + [b3] * 10, [a10] \ ([b10] + 10))
End Sub
读者可以在相关的单元格中录入数值,然后执行代码,根据结果可以看出代码涉及到的运算符的计算顺序。
如果需要让表达式按自己的方式执行,而不是总以表4-11的顺序执行,如何可以实现呢?答案是肯定的——利用括号改变计算顺序。
例如“MsgBox ([a1] + [b3]) / 100”语句中,就实现了先算加法再算除未予,从而改变了默认的优先级。
简单的字符处理函数
VBA需要处理的字符通常有三类:文本字符串、数值和日期。本节介绍VBA中常用的字符串处理函数。
字符串处理函数功能介绍
VBA常用的字符处理的函数列表如下:
表4-12 常见字符处理函数列表
作用
关键字
Option Compare
设置字符串比较规则
StrComp
比较两个字符串(字符相似判断)
StrConv
字符串类型转换
Lcase、Ucase
大小写变换
Spase、String
重复字符串
Len
计算字符串长度
Format
设置字符格式
LSet、Rset
重排字符串
InStr、Left、Ltrim、Mid、Right、Rtrim、Trim
处理字符串
Split、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) < (Z=z)
通过实例或者可以更快地明白两种方式的差异。
Option Compare Binary
Sub 字母比较()
MsgBox "a" > "A"
End Sub
本程序区分大小写比较,结果为True。
Option Compare Text
Sub 字母比较()
MsgBox "a" > "A"
End Sub
本程序不区分大小写比较,结果为False。
StrComp:字符相似比较
StrComp:返回 Variant (Integer),为字符串比较的结果。
语法:StrComp(string1, string2[, compare])
表4-13 StrComp函数比较结果
如果
StrComp 返回
string1 小于 string2
-1
string1 等于 string2
0
string1 大于 string2
1
string1 或 string 2为 Null
Null
使用 StrComp 函数来比较两个字符串。如果第三个参数值为 1,字符串是以文本比较的方式进行比较;如果第三个参数值为 0 或是默认值,则以二进制比较的方式进行比较。
Sub StrComp运算()
MsgBox StrComp("ABCD", "abcd", 1) ' 返回 0。
MsgBox StrComp("ABCD", "abcd", 0) ' 返回 -1。
MsgBox StrComp("aBCDe", "AbcDE") ' 返回 1。
End Sub
Strconv:字符串类型转换
Strconv:返回按指定类型转换的 Variant (String)。
语法:StrConv(string, conversion, LCID)
其中第二参数表示转换类型,根据不同参数可以转换成9种类型的文本。其参数与功能对应关系如下:
表4-14 Strconv第二参数与实际转换类型表
常数
值
说明
vbUpperCase
1
将字符串文字转成大写
vbLowerCase
2
将字符串文字转成小写
vbProperCase
3
将字符串中每个字的开头字母转成大写
vbWide
4*
将字符串中单字节字符转成双字节字符
vbNarro
8*
将字符串中双字节字符转成单字节字符
vbKatakana
16**
将字符串中平假名字符转成片假名字符
vbHiragana
32**
将字符串中片假名字符转成平假名字符
vbUnicode
64
根据系统的缺省码页将字符串转成 Unicode
vbFromUnicode
##
将字符串由 Unicode 转成系统的缺省码页
实例:
Sub STRCONV运算()
MsgBox StrConv("English", vbUpperCase) & Chr(10) & StrConv("English", vbLowerCase) & Chr(10) & StrConv("English", vbProperCase) & Chr(10) & StrConv("English", vbWide)
End Sub
以上代码可以将字符串“English”转换成四种效果,见图所示:
图 STRCONV函数转换字符串
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”替换成“”可以得到完全相同的结果。
图 利用Format提取日期和时间信息
LCase/ Ucase:大小写转换
LCase/ Ucase:将英文字符串进行大写与小写互换
语法:LCase(string)/ UCase(string)
函数的功能是在大写与小写字母之间转换,虽然用法简单易懂,但功能和STRCONV相去甚远,在工作中完全可使用STRCONV来替换这两个函数。实例如下:
Sub 大小写转换()
MsgBox LCase("HELLO WORLD!")
MsgBox UCase("Hello World!")
End Sub
String / space:重复字符
返回 Variant (String),其中包含指定长度重复字符的字符串。
语法如下:
String(number, character)
返回特定数目空格的 Variant (String)。
语法如下:
Space(number)
其中String函数可以对任意字符串重复N次出现,而Space则可以将空格重复N次。所以String函数包括Space的功能。以下两例可以演示String / space函数的用法
Sub 重复N次()
MsgBox String(5, "*") ' 返回 "*****"。
MsgBox String(4, "中国") ' 返回 "中中中中" 只重复左边一位
MsgBox String(5, 60) ' 返回 "<<<<<",对于未加引号的数字参数,则按字符码计算即chr(60)
End Sub
Sub 获取CD盘空间()
MsgBox "C盘:" & String(6, " ") & CreateObject("").GetDrive("C:").TotalSize / 1024 & "MB" _
& Chr(10) & "D盘:" & Space(6) & CreateObject("").GetDrive("D:").TotalSize / 1024
End Sub
其中第二个程序使用以String和Space两种方式产生6个空格,其作用完全相同。
Lset / Rset:字符串往左/右对齐
在一字符串变量中将一字符串往左对齐,或是将一用户定义类型变量复制到另一用户自定义类型变量。通俗点讲就是将字串二按钮按照字串的长度进行取舍。如果字串二长度小于字串一则以空格填充;否则截取左边字串一的长度。
语法如下:
LSet stringvar = string或者LSet varname1 = varname2
在一字符串变量中将一字符串往右对齐。和Lset函数同法一致,取右边字符。
语法:RSet stringvar = string
Sub LSET用法()
Dim Str1
Str1 = "广东省机械厂" ' 设置字符串初值
LSet Str1 = "广州本田" ' 以广州本田为参照改变Str1的值
MsgBox "[" & Str1 & "]" '因参照字符串较短,以空格填充
LSet Str1 = "广州本田制造厂" ' 以广州本田制造厂为参照改变Str1的值
MsgBox Str1 ' 因参照字符串偏长,截取多余的字符。实际工作表中参照值常是指定的单元格
End Sub
上例中演示了LSet的用法,即以一个参照字符串的长度为基准,对另一字符串左左字符取舍。
Instr:返回字符出现位置
Instr:返回指定字符串在另一字符串中最先出现的位置。
语法:InStr([start, ]string1, string2[, compare])
其中第三参数表示查找方式,在查找字母时特别需要注意设好第三参数。该参数详解如下:
表4-15 compare参数常数与功能列表
常数
值
描述
vbUseCompareOption
-1
使用Option Compare 语句设置执行一个比较
vbBinaryCompare
0
执行一个二进制比较
vbTextCompare
1
执行一个按照原文的比较
以下代码演示了在字符串中查找一个字符的方法,与工作表函数FIND相近。
Sub 查找东()
MsgBox InStr(4, "广东省东莞市东城区", "东", 1)
MsgBox InStr("广东省东莞市东城区", "东")
End Sub
Left/Mid/Right:从左、中、右取值
返回包含字符串中从左边算起指定数量的字符。
语法:Left(string, length)
返回包含字符串中从中边算起指定数量的字符。
语法:Mid(string, start[, length])
返回包含字符串中从右边算起指定数量的字符。
语法:Right(string, length)
此三个函数和工作表函数中的Left,Mid和Right用法相近,不再举例。其中Mid函数的第三参数与工作表函数Mid有所区别。工作表函数Mid的第三参数非可选参数,而VBA中的Mid函数第三参数是可选参数,在忽略第三参数的状态下,它可以取出第二参数指定位置开始的所有字符。在下面的比较中可以看出差异:
单元格A1为“中华人民共和国”,需要从第三个开始取所有字符。公式如下:
=MID(A1,3,LEN(A1))
改用VBA则可以忽略第三参数:
MsgBox Mid([a1], 3)
LTrim/RTrim/ Trim:去除空格
LTrim/RTrim/ Trim:返回其参数字符串的副本,没有前导空白 (LTrim)、尾随空白 (RTrim) 或前导和尾随空白 (Trim)。
语法分别为:
LTrim(string) 、RTrim(string)、Trim(string)
去空格在工作中运用较广。例如对单元格的字符计算前,如果它可能存在用户误输入的空格或者能确定它确实存在空格不利于计算时,都需要用此函数去空格。
Like:字符串相似度比较
Like:用来比较两个字符串。
语法如下:
result = string Like pattern
如果string与pattern匹配,则result为True;如果不匹配,则result为False。但是如果string 或pattern中有一个为Null,则resul 为Null。
Option Compare 语句的设置会直接影响ike比较的结果,所以使用Like时需要确定Option Compare模式。默认的比较方法是 Option Compare Binary。
使用Like进行字符比较时可以使用通配符。具体体现为以下的匹配模式:
表4-16 Like的匹配模式
pattern 中的字符
符合 string 中的
?
任何单一字符。
*
零个或多个字符。
#
任何一个数字 (0–9)。
[charlist]
charlist.中的任何单一字符。
[!charlist]
不在 charlist 中的任何单一字符。
Like是一个很复杂的比较方式,读者可以去帮助中详查。现在以实例演示Like在工作中的用途。
(1)按开任意工作簿,使用快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【用户窗体】;
(3)从工具箱中选择文字框(符号:)并在窗体中插拖动,而从在窗体中生成的文字框控件;
(4)双击文字框控件进入代码窗口,并输入以代码:
Private Sub TextBox1_Change()
IF Len() > 0 Then
IF Right(, 1) Like "[a-z]" Then Exit Sub Else = Left(, Len() - 1)
End IF
End Sub
(5)光标定位于代码任意位置,按下快捷键【F5】运行窗体,在文字框中随意录入字符,可以发现除小写字母外任何字都无法录入,包括数字、标点与汉字。
本例文件参见光盘:..\ 第四章\Like用法.xlsm
如果需要输入小写加大写字母,则代码中的“[a-z]”需要改为“[A-z]”。因为在二进制比较下,大写字母小于小写字母,那么“A-z”就包括了“A-Z”和“a-z”。而采用另一方式也可以完成同等功能:“[a-zA-Z]”。
如果只允许D到G之间的字母以及4-8之间的数字,其它任何字符一概拒绝,那么Like语句可以改为:Right(, 1) Like "[4-8D-G]"。
注意:Split、Join两个字符处理函数全在数组方面,本章不作介绍。在本书第13章将进行详细解说。
第五章
VBA数据类型与变量、常量
VBA代码的操作对象主要是数据。在编写代码时随时都会与各种类型的数据打交道,只有全面掌握数据类型,及代表各种数据的变量与常量才能高效地处理数据运算。
本章要点:
数据类型
常量与变量
数据类型
数据类型就是一类数据的集合。它决定变量的占用空间及变量种类。使用合理的数据类型定义变量可以使程序具有更高的执行效率。
为什么要区分数据类型
数据类型是指数据以何种方式储存在内存中。
VBA中的任何数据都有一种数据类型。数据类型可以由用户指定,称为显示声明;也可以由VBA程序自己选择,即隐式声明。然而自动分配的数据类型绝大部分时间准确,某些时候会产生错误,而且VBA分配数据类型是需要消耗内存的。也就是说在让VBA自行匹配数据类型的状态下,可以减少输入代码,却会牺牲程序的执行效率。所以在编写VBA程序时有必要定义所有变量的数据类型。
VBA中支持很多数据类型,这对新用户来户极难掌握。只有通过不断地编写、运用或者上论坛与VBA程序员在线交流,慢慢地积累经验。
认识VBA的数据类型
VBA中支持10多种数据类型。不同数据类型的差异主要体现在三方面:类型名称、占用内存空间大小和取值范围。表5-1中罗列了VBA所有V各种数据类型的空间与范围。
表5-1 VBA的数据类型
数据类型
存储空间大小
范围
Byte
1 个字节
0 到 255
Boolean
2 个字节
True 或 False
Integer
2 个字节
-32,768 到 32,767
Long(长整型)
4 个字节
-2,147,483,648 到 2,147,483,647
Single(单精度浮点型)
4 个字节
负数时从 到 -45;正数时从 -45 到
Double(双精度浮点型)
8 个字节
负数时从 到
Currency(变比整型)
8 个字节
从 -922,337,203,685, 到 922,337,203,685,
Decimal
14 个字节
没有小数点时为 +/-79,228,162,514,264,337,593,543,950,335,而小数点右边有 28 位数时为 +/;最小的非零值为 +/
Date
8 个字节
100 年1月1日到9999年12月31日
Object
4 个字节
任何 Object 引用
String(变长)
10 字节加字符串长度
0 到大约 20 亿
String(定长)
字符串长度
1 到大约 65,400
Variant(数字)
16 个字节
任何数字值,最大可达 Double 的范围
Variant(字符)
22 个字节加字符串长度
与变长 String 有相同的范围
用户自定义
所有元素所需数目
每个元素的范围与它本身的数据类型的范围相同。
从上表中分析得知,如果待计算的数据是单科成绩0到100之间,那么就应该使用Byte数据类型,用Long型虽然也可以正确执行,但它占用的内存是Byte的四倍,将使程序的效率降低;而使用String做为数据类型则会产生“类型不匹配”的错误,因为String用于文本,不适用于数值;如果非单科成绩,而是10科课程的总成绩,那么用Byte类型则会产生“溢出”的错误,因为10科成绩会大于255,而Byte数据类型的取值仅仅在0-255之间。
下面的实例将更有助于读者了解数据类型。
(1)在工作表中录入图所示数据;
(2)进入VBE界面并插入新模块,在模块窗口录入以代码:
Sub TEST()
Dim SUMS As Long, CELL As Range, I As Byte, MYSTR As String
For Each CELL In Range("A1:A10")
IF (CELL) Then SUMS = SUMS + CELL Else MYSTR = MYSTR & CELL
IF CELL = "" Then I = I + 1
Next CELL
"A1:A10中有空白单元格" & I & "个"
"A1:A10中数据和为:"; SUMS
"A1:A10中文本为:"; MYSTR
End Sub
(3)使用快捷键【Ctrl+G】显示立即窗口;
(4)光标定位于代码中任意位置并按下快捷键【F5】,执行代码后在立即窗口将显示图所示的结果:
图 工作表数据 图 统计结果
从这个程序过程可以得出以下结论:
(1)本序中使用了四个变量,分别对应四种数据类型:
表5-1 变量与数据类型
变量
数据类型
Sums
Long
Cell
Range
I
Byte
Mystr
String
(2)本例中所使用的四种数据不可以互换,任何两个变量的数据类型都无法互换。每个变量都使用了合适的数据类型。用其中Sums变量的值较大,选中Long型可以储存-2,147,483,648 到 2,147,483,647之间的数据;而Cell变量代表单元格,则必须使用Range对象类型;变量I在1到10之间变化,所以可以用最范围偏小的Byte;而Mystr变量是文字串,那么只能用String。
(3)如果本例不声明所有变量的数据类型,程序执行效率会有所下降,尽管下降不多。
数据类型的声明与转换
认识数据的类型、占用空间及定义数据类型的重要性后,就可以在程序中声明变量的数据类型了。
声明数据类型使用As语句,在As之后直接附上数据类型即可,中间用空格分隔。例如:
Dim a as Byte
数据类型不区分大小写,不管用“BYTE”、“Byte”还是“byte”都可以,但VBA会自动将录入数据类型转换成首字母大写、其它字符小写的格式。
如果不指定数据类型,则VBA自动将变量指定为变体型Variant。即以下两句代码具有相同的作用:
Dim i
Dim I as Variant
变量的数据类型根据需要也可以转换,即在声明数据类型时若使用了占用内存较大的数据类型,而在某个阶段所获取的值却很小,当用这个很小的数据去参数计算时,就有必要将该数据转换成占用内存更小的数据类型。例如:
Sub test()
Dim 成绩 As Long
成绩 = 80
'中间代码,利用变量“成绩”参与各种运算
End Sub
在以上代码,变量“成绩”的类型是占用4字节的Long型,但事实上它的值为80,那么利用变量“成绩”参与后续的各种运算将浪费内存。而将其转换成Byte型再参与运算,则可以提速,体现出类型转换之现实意义。
从另一个角度考虑,因不同的数据类型对小数处理的精度不同,那么数据转换后可以实现更符合需求的精度。
可用于数据类型转换的函数见表5-2所示:
表5-2 数据类型转换函数列表
函数
返回类型
expression 参数范围
CBool
Boolean
任何有效的字符串或数值表达式
CByte
Byte
0 至 255
CCur
Currency
-922,337,203,685, 至922,337,203,685,
CDate
Date
任何有效的日期表达式
CDbl
Double
负数从 至 -324;正数从 -324 至
CDec
Decimal
零变比数值,即无小数位数值,为+/-79,228,162,514,264,337,593,543,950,335。对于 28 位小数的数值,范围则为+/;最小的可能非零值是 。
CInt
Integer
-32,768 至 32,767,小数部分四舍五入
CLng
Long
-2,147,483,648 至 2,147,483,647,小数部分四舍五入
CSng
Single
负数为 至 -45;正数为 -45 至
CStr
String
依据 expression 参数返回 Cstr
CVar
Variant
若为数值,则范围与 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 Double
funds =
MsgBox "类型:" & TypeName(funds) & " 值:" & funds
MsgBox "类型:" & TypeName(CBool(funds)) & " 值:" & CBool(funds)
MsgBox "类型:" & TypeName(CByte(funds)) & " 值:" & CByte(funds)
End Sub
以上程序中变量的初始值是,在经过类型转换后其类型与值变化如下:
图 Double型 图 Boolean型 图 Byte型
除转换函数外,还有一种特殊的转换方式,可以在文本与数值之间转换。例如以下代码:
Sub 不同状态下的数据类型()
Dim 月份 As String, 学号 As Integer
月份 = 10
学号 = 354
MsgBox TypeName(月份 * 1)
MsgBox TypeName(学号 & "")
End Sub
代码中变量“月份”声明为文本类型String,“学号”声明为数字Integer,但文本型数字经过“*1”后可以转换成数值,其数据类型为Double;而数字型的学号连接一个空文本后自然也就成了String型。
常量与变量
变量与常量是VBA程序中不可或缺的要素。灵活运用变量与常量,可使代编写更简单、高效。
常量的定义与用途
常量也称常数,在帮助中,Excel对常量的定义为“执行程序时保持常数值的命名项目。常数可以是字符串、数值、另一常数、任何( 除乘幂与 Is 之外的) 算术运算符或逻辑运算符的组合。”如果更通俗地讲,常量就是在程序执行过程中永远不变的量。
“你我他”是一个常量,“Asc”是一个常量,128也是一个常量。
常量的用途主要体现在两方面:
1.简化输入
在某个程序中需要多次使用一个数值时,如果每次都录入这个长长的数值,效率会降低,同时也有录入错误的潜在危险,例如小了一位数,或者多了一个小数点。
通常的解决办法是定义一个常量“P”,对常量赋值为,后续的操作时直接用P参与运算。结果完全相同,但在录入时却轻松许多。
2.方便识别
仍是前一个实例来说明,在程序中大量运用数据,而程序的终端用户却不一定知道这数据是代表什么。如果我们在代码中定义一个常量“圆周率”,并对其赋值为,岂非可以顾名思义?
常量的声明方式
声明常量可以使用Const语句来完成。
Const的语法如下:
[Public | Private] Const constname [As type] = expression
其中Public是可选的,该关键字用于在模块级别中声明在所有模块中对所有过程都可以使用的常量,在过程中声明常不能使用Public;
Private也是可选的,该关键字用于在模块级声明只能在包含该声明的模块中使用的常数,不能在过程中使用;
Constname是必需的参数。用于指明常数的名称;
Type参数是可选的,表示常数的数据类型。
Expression是必需的参数。用于指定文字、其它常数,或由除Is之外的任意算术操作符和逻辑操作符所构成的任意组合。
例如声明一个常量可以用以下语句:
Const 圆周率 As Double =
Const 圆周率 =
Public Const 圆周率 As Double =
第一句声明语句指定了常量名称和常量类型,第二句未指定常量类型;第三句使用了Public,必须在模块顶部使用,而不能在过程是使用。图即为错误声明常量的提示。
图 在模块中使用Public时的错误提示
正确的申明方式为:
Public Const 圆周率 As Double =
Sub test()
MsgBox 圆周率
End Sub
常量的命名规则
常量有两类:内置常量和用户定义常量。
其中内置常量是Excel定义的,用户直接调有即可。例如:xlLandscape和xlPortrait。这两个常分别代表2和1,用于设定页面的方向是横向还是纵向,在VBA中可以调用这两个常量,也可以直接用2和1。
还有Msgbox的参数“vbDefaultButton1”、“vbCritical”等等也是内置常量。
用户定义的常量则需要按一定规则来定义后才可以使用。常量命名规则如下:
(1)第一个字符必须使用英文字母或者汉字;
(2)不能在常量名称中使用空格、句点(.)、惊叹号(!)、或 @、&、$,# 等字符;
(3)名称的长度不可以超过 255 个字符;
(4)常量名称不能与其自身的Function过程、语句以及方法的名称相同。如果常量与内置函数同名,那么在调用内置函数时可将内置函数、语句或方法的名称之前加上关联的类型库的名称。例如,如果有一个名为Mid 的变量,则多方面要使用“”来调用Mid 函数,否则只能调用常量;
(5)不能在范围的相同层次中使用重复的名称,但不同级别的代码中却可以使用。例如在模块中使用一个“Arr”的公有常量,在一个过程中也可以同时再定义一个名为“Arr”的私有常量。
(6)不能与VBA的保留字一致。例如Dim和Sub、End等等;
(7)常量名称不区分字母大小写;
(8)在常量名称中间可以使用标点符,也可以使用分隔符_来区分多个单词。例如:Coloer_Count或者Sum_byte等等。
下面例举一些不符合要求的常量声明方式:
Const 12st as Byte
Const ?asc as Integer
Const dim As Byte = 11
Const sub as String
Const "ABC" As String
变量的定义与用途
变量是一个已经命名的存储位置,它包含了程序每个执行阶段所修改的数据。每一变量都有变量名,在其范围内可唯一识别。更通俗地说,变量就是在程序执行可中以随时变化的量。
变量和常量一样,也具有简化输入和方便识别两个优点,同时变量有一个常量不具备的优点:储存不确定的数据。特别在循环语句中,待处理的数值随时会变量变化,常量不足以满足需求,而只能借助常量。
例如:
Sub 显示用户名()
User_name = ("请输入您的姓名", "姓名", , , , , , 2)
MsgBox User_name
End Sub
在执行在以上代码时,会弹出一个对话框让用户录入姓名,然后弹出一个与录入的姓名一致的消息框。见图和所示。
图 录入姓名 图 反馈信息
代码中User_name就是一个变量,它代表一个不确定的值。因为没有人知道终端用户会录入什么字符,而利用一个变量来取代它参与其它的所有运算或者显示是最高效的作法。
从下面的实例,读者可以得到一个真实感受:声明变量对效率的大幅提升:
Sub 测试一()
Dim tim As Long, x As Integer, y As Integer, z As Integer
tim = Timer
For x = 1 To 10000
For y = 1 To 10000
z = x + y
Next y, x
MsgBox "时间:" & (Timer - tim)
End Sub
Sub 测试二()
tim = Timer
For x = 1 To 10000
For y = 1 To 10000
z = x + y
Next y, x
MsgBox "时间:" & (Timer - tim)
End Sub
分别执行以上两代个码,可以现发现“测试二“的执行时间是“测试一”的两到三倍,而它们达成的目的相同,区别仅仅是“是否声明变量类型”。
变量的类型与声明
变量的类型与常量一致,参见表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参数说明
部分
描述
WithEvents
可选的。声明varname是一个用来响应由 ActiveX 对象触发的事件的对象变量。只有在类模块中才是合法的
varname
必需的。变量的名称;遵循标准的变量命名约定
subscripts
可选的。数组变量的维数;最多可以定义 60 维的多维数组
New
可选的。可隐式地创建对象的关键字。如果使用 New 来声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用 Set 语句来给该对象引用赋值。New 关键字不能与WithEvents一起使用
type
可选的。变量的数据类型。所声明的每个变量都要一个单独的 As type 子句
下面例举一些常见的变量声明方式:
Dim name
Dim a As Byte
Dim 姓名 As String
Dim WithEvents xlApp As Application
Dim声明变量时允许同时声明多个,也允许部分指定数据类型、部分不指定数据类型。在同一行中只需要使用一个DIM语句。例如:
Dim a, b, c——声明了三个变体型(Variant)变量
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, b
c As String
以上代码第二行未使用Dim语句。
另外一点,变量与常量一样也不能声明为Excel的保留字。包括Sub、End、Next和End等等。如“dim next as String”是一个不合法的声明。
区分静态变量与动态变量
动态变量是指每次被过程调用时均重新初始化的变量,而静态变量在初始化后,下一次调用时保留上次的值的变量。静态变量和动态变量的区别由Static和Dim等不同声明方式引起的。
Static语句用于声明静态变量。在模块的代码开始运行后,使用Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用Static语句显式声明只在该过程内可见、但具有与包含该过程定义的模块相同生命期的变量。
Static语句声明变量和Dim语句的语法相同,不同处在于变量的值。Static语句声明的变量会一直保持其值,而Dim语句声明的变量会在过程结束时释放其值。
下面的实例可以比较两种方式声明变量的区别:
Sub 动态与静态()
Dim A As Byte
Static B As Byte
A = A + 1
B = B + 1
"A等于" & A
"B等于" & B
End Sub
连续执行三次主面的代码,在立即窗口中显示的结果见图所示。
图 动态变量与静态变量的区别
从上面的代码可以看出,静态变量在过程执行完毕后变量的值一直保存在内存中;而动态变量却在程序完成时归零或者返回空值。
变量的作用域与生命周期
变量的作用域是指允许在什么地方调用变量。
变量分为公有变量和私有变量,也称模块级变量和过程级变量。这种差异取决于声明方式。其中私有变量的作用域是当前过程,而公有变量允许多个过程调用同一变量。具体表现如下:
表5-4 变量的作用域
作用域
变量的声明方式
当前过程
在本过程中利用Dim或者Static声明
当前模块
在本模块内第一个过程前用Dim或者Private声明
所有模块
在模块内第一个过程前用Public声明
现举例演示:
Dim TEMP As Byte
Sub A()
TEMP = 10
MsgBox TEMP
End Sub
Sub B()
TEMP = TEMP + 10
MsgBox TEMP
End Sub
在一个模块代码窗口中,在Sub过程之前声明了一个变量temp,那么这个变量是模块级变量。它可以在该模块的任何过程中调用。
当用户运行第一个过程后,temp已赋值为10,再执行第二个过程时,temp的结果等于20。这是因为第一个过程改变了本模块公有变量temp的值,而第二个过程调用变量temp时,仍保留了temp在第一个过程中的值。
再看看一个过程级别的变量用法?
Sub A()
Dim TEMP As Byte
TEMP = 10
MsgBox TEMP
End Sub
Sub B()
Dim TEMP As Byte
TEMP = TEMP + 10
MsgBox TEMP
End Sub
这两个过程都声明了变量temp,但两个变量是不相干的。第一个过程执行后temp值为10,第二个过程序执行时,变量temp将会重新初始化,而不会保留过程A中temp的值。
除Dim语句外,还可以使用Private 语句来声明变量。
Private 语句:在模块级别中使用,用于声明私有变量及分配存储空间。
使用Private 语句来声明变量,可以使变量在本模块中使用,和前面Dim语句声明模块级变量一致。
声明变量还有一种语句: Public 语句
Public 语句:在模块级别中使用,用于声明公用变量和分配存储空间。
Public 语句在所有应用程序的所有没有使用 Option Private Module 的模块的任何过程中都是可用的;若该模块使用了 Option Private Module,则该变量只是在其所属工程中是公用的。
举一个实例:
在模块1中输入以下代码:
Public TEMP As Byte
Sub A()
TEMP = 10
MsgBox TEMP
End Sub
在模块2中输入以下代码:
Sub B()
TEMP = TEMP + 10
MsgBox TEMP
End 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后才会终止,除非人工消毁变量。
认识对象变量
对象变量也是变量的一种,但却有它独有的特殊性。
对象变量是代表一个对象,不是一个内存中的一个值。对象包括工作表对象、工作表簿对象、单元格对象,甚至Excel应用程序对象。
对象变量的声明方式与其它变量一致,不再赘述。但变量的赋值方式不同:
Let方法:用于对象以外的变量赋值,是可选参数
Set方法:对对象变量进行赋值,是必选参数
例如对非对象变量赋值,可以用:
Let A=10
A=10
但如果变量A是单元格对象时,则需要用以下方式赋值:
Set A = Range(“A10”)
Set A = [A10]
对象变量在初始化时值为Nothing,可以理解为“什么也没有”。而在变量赋值后它就代表了一个对象,如A1单元格,一个工作表或者一个应用程序。
在程序结束前可以销毁这个对象变量,方法如下:
Set a = Nothing
程序中使用对象变量有两个优点:简化多级对象的录入和提升程序的执行效率。
能过下例程序的比较也可以证明对象变量的优势:
Sub 设置A10的字体()
("Sheet2").Range("A10"). = "黑体"
("Sheet2").Range("A10"). = 3
("Sheet2").Range("A10"). = 5
End Sub
Sub 设置A10的字体二()
Dim rng As Range
Set rng = ("Sheet2").Range("A10")
= "黑体"
= 3
= 5
End Sub
以上第一个程序在输入时费时很多,而第二个程序中利用一个简单的变量Rng替代“("Sheet2").Range("A10")”后直接参与后续的所有操作,有助于快速录入代码,且避免潜在的录入错误。
另一个优点是程序二中执行效率较第一个更高。如果一定要测试它们在效率上差异多少,可以将程序循环10000次再做比较,便于直观的分辨时间差异。
Sub 设置A10的字体()
tim = Timer
For i = 1 To 10000
("Sheet2").Range("A10"). = "黑体"
("Sheet2").Range("A10"). = 3
("Sheet2").Range("A10"). = 5
Next
MsgBox Timer - tim
End Sub
Sub 设置A10的字体二()
tim = Timer
Dim rng As Range
Set rng = ("Sheet2").Range("A10")
For i = 1 To 10000
= "黑体"
= 3
= 5
Next
MsgBox Timer - tim
End Sub
分别执行以上两段程序,可以得到两个执行时间。它们之间差大概在3秒左右。
认识数组变量
VBA中也支持数组变量。数组是一组具有相同性质的数据的集合,在VBA中使用数组可以大的提升程序执行效率。
数组的声明方式和其它的变量是一样的,它可以使用 Dim、Static、Private 或 Public 语句来声明,但它们的不同点在于数组通常需要指定大小。数组的大小被指定后,则它是个固定大小数组,若程序运行时数组的大小可以被改变,则它是个动态数组。
数组的具体应用请参阅本书第13章。
第六章
认识VBA过程及开发自定义函数
VBA的主体结构就是过程。VBA包括子过程、函数过程和属性过程三种,本书主要介绍子过程(也称Sub过程)和函数过程(也称Function过程)。
本章要点:
认识过程
SUB过程
Function过程
关于过程参数
开发自定义函数
编写函数帮助
认识过程
VBA中每一个程序都包含过程。录制的宏是一个过程,一个自定义函数也是一个过程。掌握好单个过程的编写与思路,就可以组合成一个大中型插件或者专业程序。
过程的分类与调用方式
过程主要分为三类:子过程、函数过程和属性过程。这三类过程分别为以下三种格式
Sub 子过程()
……
End Sub
Function 函数过程(rng As Range)
……
End Function
Property Get 属性过程() As Variant
……
End Property
Property 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】调出图所示对话框,然后选择“新建菜单”,并单击“选项”按钮,在弹出的“宏选项”对话框中指定快捷键,见图所示。图片设计宏程序“新建菜单”的快捷键是【Ctrl+q】。
图 “宏”对话框 图 设置Sub过程的快捷键
3.按钮执行
可以工作表中建一个按钮,并将按钮与Sub过程关联,从而实现单击按钮执行程序。
将按钮关联到Sub过程的步骤为:
(1)单击菜单【开发工具】\【表单控件】\【按钮】;
(2)在工作表中按下左键并向右下方拖动,从而绘制一个控件按钮;
(3)在弹出的对话框中选择“新建菜单”,见图所示;
(4)返回工作表后即可单击名为“按钮1”的按钮来执行程序“新建菜单”。
图 关联过程与按钮
4.菜单调用
最常见的是编写一个自定义菜单或者工具条来调用Sub过程。菜单与工具条的设计方法参见本书22章及23章。
5.事件引发
对于部分需要自启动的程序,通常利用事件事引发,不需要人工干预。例如工作簿开启时就自动执行某程序,或者关掉窗体、鼠标移过窗体时执行某程序…….
对于事件过程的运用参见本书12章。
6.工作表中使用公式调用
Function过程即自定义函数,可以像使用内置的工作表函数一样在公式中使用。
调用Function过程的步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块中录入以代码:
Function 成绩(rng)
成绩 = IIF(rng >= 60, "及格", "不及格")
End Function
(3)返回工作表中,在A1输入数值50,在B1输入公式:
=成绩(A1)
可以发现公式可以像内置函数一样运行,它返回“不及格”,正是期望的结果。
插入过程的方式
编写过程时可以手工录入代码,也可以让利用VBA提供的列表自动产生程序外壳。
一个Sub过程分为程序外壳部分和主体部分。例如:
图 Sub过程的外壳与主体部分示意图
其中外壳部分可手工以录入,也可以利用VBE提供的方式完成。
1.非事件过程
对于非事件的Sub 过程,VBA提供了一个专用窗体来选择性录入过程的外壳。具体步骤如下:
(1)在VBE界面中单击菜单【插入】\【模块】;
(2)单击菜单【插入】\【过程】打开“添加过程”对话框;
(3)在“中称”框中录入“汇总”,并将“类型”选择“子过程”,将“范围”设为“私有”,见图所示。然后单击“确定”按钮。
执行以上程序后在模块中可以看到产生的代码为:
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 Sub
图 添加Sub过程外壳图 从下拉列表选择对象
因为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, ByVal X As Single, ByVal Y As Single)
End Sub
(4)删除“UserForm_Click”事件的代码。
过程的命名规则
过程的命名与变量的命名规则一致。
但要补充一点是:过程名可以和本过程的私有变量同名,但却不能和公有变量同名。例如:
Sub 身份证()
Dim 身份证 As String
身份证 = [a1].Text
End Sub
以上代码中过程与变量同名,但这是允许的。
Dim 身份证 As String
Sub 身份证()
身份证 = [a1].Text
End Sub
这段代码却是非法的,只要运行程序就会弹出编译错误。
为了避免错误及便于识别,既使本过程的私有变量也尽量保持与过程名不相同。
编写SUB过程
本节开始了解关于Sub过程的基本概念,以及编写简单的Sub过程。
SUB过程的语法解析
Sub过程即利用Sub语句声明的过程。所以宏录制器产生的过程全是Sub过程,无法通过录制宏产生Function过程或者属性过程。
Sub语句声明过程的语法如下:
Private | Public | Friend] [Static] Sub name [(arglist)]
[statements]
[Exit Sub]
[statements]
End Sub
其中各参数的详细功能见表6-1所示:
表6-1 Sub语句参数详解
参数部分
功能解释
Public
可选的。表示所有模块的所有其它过程都可访问这个 Sub 过程。 如果在包含 Option Private 的模块中使用,则这个过程在该工程外是不可使用的
Private
可选的。表示只有在包含其声明的模块中的其它过程可以访问该 Sub 过程
Friend
可选的。只能在类模块中使用。表示该 Sub 过程在整个工程中都是可见的,但对对象实例的控制者是不可见的
Static
可选的。表示在调用之间保留 Sub 过程的局部变量的值。Static 属性对在 Sub 外声明的变量不会产生影响,即使过程中也使用了这些变量
name
必需的。Sub 的名称;遵循标准的变量命名约定
arglist
可选的。代表在调用时要传递给 Sub 过程的参数的变量列表。多个变量则用逗号隔开
statements
可选的。Sub 过程中所执行的任何语句组
Sub过程与所有变量一样,也区分公有和私有,而在说法上稍有区别。过程分模块级过程和工程级过程。
1.模块级过程
模块级过程即只能在当前模块调用的过程,它的特征有三处:
(1)声明Sub过程前使用Private;
(2)只有当前过程可以调用,例如在“模块1”中有以下代码:
Private Sub 过程一()
MsgBox 123
End Sub
Private Sub 过程二()
Call 过程一
End Sub
执行过程二时可以调用过程一,但如果过程二存放于“模块2”中,则将弹出“子过程未定义”的错误提示。
(3)不出现在“宏”对话框中。即使用快捷键【Alt+F8】所打开对话框中无法查看到当前过程的名称列表。如果是Function过程,则无法在函数向导中查看到函数名。
提示:所有事件的代码都是过程级的,默认状态下只有在当前过程可以调用。
2.工程级过程
工程级过程是指在当前工程中任意地方都可以随意调用的过程。它的特征刚好与模块级过程相反:在“Sub”语句前置标识符“Public”、非当前过程可以调用,可以出现在“宏”对话框中。
如果一个过程没有使用“Public”和“Private”标识,则默认为公有过程,任何模块或者窗体中都可以调用。
Sub过程也支持参数,其参数的用法与Function过程的参数用法一致,本小节不详述,请参阅要书“ 关于过程参数”。
3.中途退出程序的多种方法与分别
Sub过程可以在程序中间任意位置退出程序,通常是设定的个件条件,当满足条件时使用“Exit Sub”来退出程序。当程序退出后,后面的代码不再执行。
也可以使用“End”来退出程序。“End”和“Exit Sub”在使用中有相同处,也有明显的分别。相同处是都可以中途终止程序的运行,不是处则有以下两点:
是否释放公有变量
从下三段代码可以体现“End”和“Exit Sub”的差异:
Dim x As Long
Sub A()
x = 888
Exit Sub
End Sub
Sub B()
x = 888
End
End Sub
Sub C()
MsgBox x
End Sub
代码中X是公有变量,当执行过程A后执行过程C,那么变量X的值为888,表示X变量的值在过程中并没有释放,“Exit Sub”仅仅退出程序执行,公有变量的值保护不变。
如果执行过程B再执行过程C,那么X的值则为0,说过在过程B中的“End”已经释放变量X的值。
是否终止所有程序
仍然用三种个过程来演示“End”和“Exit Sub”的差异:
Sub A()
Call B
MsgBox "终止"
End Sub
Sub B()
Exit Sub
End Sub
Sub C()
End
End Sub
执行程序A的结果是弹出对话框“终止”,而将过程A中的“Call B”修改为“Call C”,那么什么反应也没有。也就是“Exit Sub”是退出它所在的程序,而“End”则中止所有程序,包括调用它的程序。如果在窗体代码中,“Exit Sub”仅仅退出事件,而“End”则退出事件后关掉窗体,窗体中声明的所有变量全部释放。
Sub过程的执行流程
如果录用宏并执行宏,可以看出宏代码的执行流程永远是从上到下。可以使用调试功能来查看流程。例如执行以下代码:
Sub 设置A1单元格()
Range("A1").Select
Range("A1") = "中华人民共和国"
Range("A1"). = 65535
Range("A1"). = 3
Range("A1"). = xlContinuous
Range("A1"). = "黑体"
Range("A1"). = 20
Range("A1").
End Sub
将VBE窗口缩小,使自己能同时看到代码及A1单元格的情况下再按下快捷键【F8】,从而进入逐句调试阶段。
注意:在VBE中使用【F8】键表示调试语代码句,每按下一次【F8】即执行一句,忽略变量与常量的声明语句,直到“Exit Sub”或者“End”、“End Sub”。在编写代码时非常有用,可以借助它检查代码的准备性,同时也可以查看程序间的跳转是否正常(当有标签设置和嵌套调用的时候)。
当按下调试键【F8】时当前执行的语法呈黄色显示,再次按下【F8】时则下一句呈黄色显示,而操作对象A1则对应产生变化。图中已执行到第四句,所以A2单元格同步后的状态就是录入“中华人民共和国”后并设置了背景色为黄色。
图 逐步执行代码
当继续通过【F8】键执行完成的代码后,可以得出结论:所有录制的宏和未特别指定程序跳转的VBA代码总是从上至下的流程逐句执行。
那么是否有例外呢?通常在三种情况下会例:
1.使用冒号使一行执行多句代码
VBA中允许借助冒号将多句代码写在同一行执行。对同行中的代码按从左向右的顺序执行。例如:
Sub 设置A1单元格()
Range("A1") = "中华人民共和国": Range("A1"). = 65535
Range("A1"). = 3: Range("A1"). = 20
Range("A1").
End Sub
以上代码在借助冒号将四行代码缩至两行,但执行过程仍然会为四步。对于同行中有多句代码时,按从左向右的顺序执行。
那么读者一定可以想到,使用冒号和不使冒号的执行结果岂非完全一致?仅仅改变了行数?
答案是“有时一致,有时不一致”。如果以上的代码按如下方式编写,那么结果完全一致:
Sub 设置A1单元格()
Range("A1") = "中华人民共和国"
Range("A1"). = 65535
Range("A1"). = 3
Range("A1"). = 20
Range("A1").
End Sub
而在下面的代码中,使用冒号后却可以得到完全不同的结果:
Sub 判断是否及格1()
IF [B2] >= 60 Then [C3] = "及极": Exit Sub
IF [B3] >= 60 Then [C3] = "及格"
End Sub
Sub 标示最大值2()
IF [B2] >= 60 Then [C3] = "及极"
Exit Sub
IF [B3] >= 60 Then [C3] = "及格"
End Sub
假设工作表中有图所示数据,执行过程“判断是否及格1”时,C3单元格将出现“及格”;而执行过程“判断是否及格2”时则无任何反应。也就是说“Exit Sub”语句与IF同行时,只有单元格B2的值大于等于60,“Exit Sub”语句才会执行。在本例中不符合条件,那么没有退出程序,可以继续执行其后的代码。而“Exit Sub”语句单独占居一行时,不管单元格B2是否符合条件,“Exit Sub”都会执行,从而退出程序,不再对B3的值进行判断。
图 数据
2.使用标签改变执行流程
VBA可以在代码中设置一个或者多个标签,然后让程序在满足某条件时跳转到标签处,从而改变过程执行流程。
标签的规则是:
可以是标点符号以外的字符组合
以冒号(:)结尾
与大小写无关
必须位于一行的最左端
配合Goto使用
例如,建立一个名为“总表”的工作表,代码如下:
Sub 新建总表()
For i = 1 To
IF Sheets(i).Name = "总表" Then GoTo err
Next i
= "总表"
End
err:
MsgBox "已经存在总表"
End Sub
以上代码首先利用For循环逐一检查工作表的名字,如果某个工作表的名字等于“总表”则执行标签“Err”之后的代码,否则继续执行For循环,直到循环完成并新建一个工作表、命名为“总表”。
使用标签完成当前程序间的跳转时需要注意两点:
(1)标签名后面必须带有冒号。
(2)在标签之前根据需要,及时退出程序。
在本例中,按照设计意图,只要工作簿中存在“总表”则执行标签“Err”之后的语句,反之不执行。所以标签之前必须加入“End”或者“Exit Sub”来退出程序,否则任何情况下Err后的语句都会被执行。
在一个过程中还可以定义多个标签。例如:
Sub 新建总表()
MsgBox
IF = True Then GoTo 已加密
For i = 1 To
IF Sheets(i).Name = "总表" Then GoTo 已存在
Next i
= "总表"
End
已存在:
MsgBox "已经存在总表"
End
已加密:
MsgBox "当前工作簿窗口已锁定,无法建立新表"
End Sub
在此过程中,首先判断当前工作表的窗口是否锁定,如果锁定则执行“已加密”标签后的语句;然后再检查是否存在“总表”,当有“总表”时执行“已存在”标签后的语句。本例中两个标标签没有顺序上的差异,谁前谁后不影响代码的结果。
过程的嵌套调用方式
过程与过程之间是可以相互调用的,从而使代码的执行流程改变。通过VBA代码调用Sub子过程主要有两种方式。
Call语句
Call语句的功能是将一个过程的控制权转移到一个过程。
它的语法为:[Call] name [argumentlist],即Call 过程名 参数。
其中Call是可选的,即在其它过程调用过程一可以有以下两种形式:
Sub 过程一()
MsgBox "你好!"
End Sub
Private Sub 过程二()
过程一
End Sub
Private Sub 过程三()
Call 过程一
End Sub
过程二和过程三都是合法的过程调用。
Run方法
Run方法可以运行一个宏或者调用一个函数。该方法可用于运行用 Visual Basic 或 Excel 宏语言编写的宏或者运行DLL或XLL中的函数。实例如下:
Sub 过程四()
"过程一"
End Sub
其中“ ”也可以简写为“Run”。
过程的递归
所有过程都是可以递归的,即可以调用自己来完成任务。
实际工作中需要调用过程自己的实例极少,通常进入递归都是编码有问题而误进入递归状态,结果耗尽系统资源。
在某些情况下也可以故意调用自己来完成任务。例中:
1.按条件新建工作表
Sub 建立10个表()
IF >= 10 Then Exit Sub
, Sheets(), 1
Call 建立10个表
End Sub
以上代码中,首先利用IF查测当前工作簿的工作表数量,如果大于等于10则退出程序,否则在最后位置新建一个工作表,最后再调用自身继续执行,直接满足条件“大于等于10”为止。
因代码中人为设置了退出递归的条件,所以这类递归不会造成程序崩溃,资源耗尽。如果将代码中的“IF >= 10 Then Exit Sub”删除,那么程序循环执行的结果就是电脑死机,除非中途人工中断程序执行:使用快捷键【Ctrl+Break】。
2.设计时钟
Sub 时间()
[a1] = (Now(), "hh:mm:ss")
Now() + TimeValue("00:00:01"), "时间"
End Sub
Sub 终止()
Now() + TimeValue("00:00:01"), "时间", , False
End Sub
以上代码实现的效果为在单元格显示当前时间,包括时、分、秒,且每秒钟更新一次。通过递归方式让程序每秒钟执行一次实现时钟的效果,同时再利用另一个过程来随时退出递归。当然也可以改用快捷键【Ctrl+Break】。
SUB过程实例演示
为了更好的理解Sub过程,通过两个例来展示。
统计选区信息:不带参数的Sub过程
要求:对任意选区进行单元格个数、数值个数、非空单元格个数、空白单元格个数及选区之和统计。
代码如下:
Sub 选区统计()
Dim msg As String
msg = "单元格个数:" & & Chr(10)
msg = msg & "数字个数:" & (Selection) & Chr(10)
msg = msg & "非空单元格:" & (Selection) & Chr(10)
msg = msg & "空白单元格个数:" & (Selection) & Chr(10)
msg = msg & "选区之和:" & (Selection)
MsgBox msg, 64, "选区统计"
End Sub
假设工作表中存在图所示数据,选择A1:D9区域后利用快捷键【Alt+F8】执行“选区统计”过程,其统计结果见图所示。
图 工作表数据 图 选区统计结果
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”。
认识Function过程
Function过程即自定义函数,在插件中应用极广。本节介绍关于Function过程的语法及调用方法。
Function过程的特点
Function过程的功能较Sub过程的应用范围稍小,Function过程仅仅用于返回一个值或者多个数的组合即数组,而Sub过程可以返回值,还可以对引用的对象进行修改,例如引用单元格A1的值后对单元格A1设置新的格式,或者修改工作表名称等等。Function可以获取工作表名称,但无法修改工作表的名称。
Function过程可以不使用参数,类似于工作表函数Rand和Now等等,但绝大部分函数是需要一个参数或者多个参数的,最多时可达255个参数。
Function的语法解析
Function的语法如下:
[Public | Private | Friend] [Static] Function name [(arglist)] [As type]
[statements]
[name = expression]
[Exit Function]
[statements]
[name = expression]
……
End Function
Function的各参数详解如下:
表6-2 Function语句参数详解
参数部分
功能解释
Public
可选的。表示所有模块的所有其它过程都可访问这个 Function 过程。如果是在包含 Option Private 的模块中使用,则这个过程在该工程外是不可使用的
Private
可选的。表示只有包含其声明的模块的其它过程可以访问该 Function 过程
Friend
可选的。只能在类模块中使用。表示该 Function 过程在整个工程中都是可见的,但对于对象实例的控制者是不可见的
Static
可选的。表示在调用之间将保留 Function 过程的局部变量值。Static 属性对在该 Function 外声明的变量不会产生影响,即使过程中也使用了这些变量
name
必需的。Function 的名称;遵循标准的变量命名约定
arglist
可选的。代表在调用时要传递给 Function 过程的参数变量列表。多个变量应用逗号隔开。
type
可选的。Function 过程的返回值的数据类型,可以是 Byte、 Boolean 、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(除定长)、Object、Variant或任何用户定义类型
statements
可选的。在 Function 过程中执行的任何语句组
expression
可选的。Function 的返回值
和Sub过程一样,Function过程也有模块级过程和工程级过程之分。Function前置“Public”即为过程级,前置“Private”则为模块级。
Function名称在声明时需要遵循与Sub过程一样的规则。
如果自定义的Function名称与VBA内部名称一致,仍然可以正常执行,只是以代码中调用中名的内部函数时必须声明其对象库。例如:
Function sqr(AA)
sqr = AA ^ (1 / 3)
End Function
Sub test()
MsgBox ":" & (27) & Chr(10) & "SQR:" & sqr(27)
End Sub
在以上代码中,执行test过程时的结果如图所示:
图 自定义SQR和内置SQR的分别
从结果可以得知,在代码使用“”可以调用VBA自带的SQR功能,而直接使用SQR则调用自定义的SQR函数的功能。
虽然定义函数时允许与内部函数一致,但却不允许与定义的变量或者常量一致,不管这个变量或者常量是本过程私有的还有模块中公有的,否则将产生“发现二义性的名称”的编译错误。
调用Function过程
Function过程通常三种方式调用:
(1)在工作表中通过公式调用:像内部函数一样在工作表中使用,也可以与其它函数嵌套。
(2)在VBA代码中被其它过程调用:就像图对应的那段代码一样在Sub过程调用函数。
(3)递归:Function过程和Sub一样可以实现递归。如果不是刻意地、有计划地进入递归状态,可以会造成资源耗尽或者溢出堆栈空间。例如下例函数的调用:
Function 递归(参数)
递归 = 递归(参数)
End Function
Sub 测试()
MsgBox 递归(1000)
End Sub
将代码录入到模块中后,执行过程“测试”,立即弹错误提示“溢出堆栈空间”。
为了避免递归造成的错误,甚至程序崩溃,尽量不要调用自身,开发函数、插件时多方面查核是否可能造成循环引用、递归现象。当然,有目的、有条件的递归也可以给工作带来的便利的。
另外,谈到函数就不能不说它的“刷新”性能,即在工作表中使用函数时,当在其它区域的数据更新时,当前单元格的函数是否重新运算,专业术语称之为“易失性”。
用户定义的函数是否有易失性可以使用以下语句来控制:
该语句的作是无论何时在工作表的任意单元格中进行计算时,让函数都必须重新进行计算。即工作表刷新时调用函数再运算一次,从而实现数据更新,让公式结果同步。
关于过程的参数
Sub过程和Function过程都可以使用参数。有参数的过程相对无参数的过程更具灵活性,相当于给了用户更多自定义的空间。
SUB过程的参数及应用
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 Sub过程参数详解
部分
功能详解
Optional
可选的。表示参数不是必需的关键字。如果使用了该选项,则 arglist 中的后续参数都必须是可选的,而且必须都使用 Optional 关键字声明。如果使用了 ParamArray,则任何参数都不能使用 Optional
ByVal
可选的。表示该参数按值传递
ByRef
可选的。表示该参数按地址传递。ByRef 是 Visual Basic 的缺省选项
ParamArray
可选的。只用于 arglist 的最后一个参数,指明最后这个参数是一个 Variant 元素的 Optional 数组。使用 ParamArray 关键字可以提供任意数目的参数。ParamArray 关键字不能与 ByVal,ByRef,或 Optional 一起使用
varname
必需的。代表参数的变量的名称;遵循标准的变量命名约定
type
可选的。传递给该过程的参数的数据类型,如果没有选择参数 Optional,则可以指定用户定义类型,或对象类型
defaultvalue
可选的。任何常数或常数表达式。只对 Optional 参数合法。如果类型为 Object,则显式的缺省值只能是 Nothing
从表中可以看出,如果需要给Sub过程设置一个可选参数,则可使用关键字Optional来声明,如果需要设置多个可选参数,则可使用关键字ParamArray来声明参数。
下例即使用一个参数的Sub过程:
Sub 过程一(msg As String)
IF Len(msg) <> 0 Then MsgBox msg, 64, "友情提示"
End Sub
Private Sub 过程二()
Call 过程一("你好")
End Sub
如果执行过程二,将弹出图所示对话框。
图 提示信息
可能看到以上代码时有读者会存在疑问?直接在过程二中执行Msgbox不是更简吗?例如改成以下代码:
Private Sub 过程二()
MsgBox "你好", 64, "友情提示"""
End Sub
在本例中确实二合一后更简单,但当有很多过程需要执行类似操作时,则在一个过程时进行判断比每个过程都判断一次会简单。例如:
Sub 姓名(name As String)
Dim i As Byte, rng As Range
For i = 1 To
IF (i).name = "许可人员列表" Then: GoTo OK
Next i
MsgBox "不存在“许可人员列表”", 64
Exit Sub
OK:
IF Len(name) < 2 Or Len(name) > 4 Then MsgBox "长度只能2到4,请重新录入", 64: Exit Sub
Set rng = ("许可人员列表").Range("a1:a10").Find(name)
IF rng Is Nothing Then MsgBox "你无操作权限" Else MsgBox "你具有操作权限"
End Sub
Sub 确认权限一() '手工指定姓名
Call 姓名(("请输入您的姓名", "确认权限", "", , , , , 2))
End Sub
Sub 确认权限二() '以当前表A1的值进行判断
Call 姓名(("A1"))
End Sub
Sub 确认权限三() '以OFFICE安装用户名进行判断
Call 姓名()
End Sub
以上代码用于判断指定的用户名是否具有操作权限。在工作簿中有一个工作表名为“许可人员列表”,该表中A1:A10存放以10个允许操作的人员名单。程序会将用户输入或者指定方式获取的姓名与A1:A10中允许的姓名进行比较,如果与任何一个一致则提示“你具操作权限”,否则提示“你无操作权限”。
在过程“确认权限一”、“确认权限二”和“确认权限三”中都可以调用过程“姓名”,只是参数不同。如果不使用过程“姓名(Name)”作过渡的话,那么过程“姓名(Name)”中的所有代码需要在后面三个过程中出现三次,每一个过程都需要对参数进行多次判断及循环,从而整个工程的代码偏长。
本例文件参见光盘:..\ 第六章\确认权限.xlsm
下例再演示具有两个参数但第二个参数是可选参数的Sub过程:
Sub 改名(Sht_Name As String, Optional i As Byte = 1)
Dim j As Byte
For j = 1 To
IF Sheets(j).name = Sht_Name Then MsgBox "已存在:" & Sht_Name, 64: End
Next j
IF i >= 1 And i <= Then Sheets(i).name = Sht_Name
End Sub
Private Sub 过程二()
Call 改名("总表", 12)
End Sub
Private Sub 过程三()
Call 改名("汇总表")
End Sub
以上过程用于工作表改名,根据指定的工作表新名称与工作表序号对工作表重命名。
在以上代码中,过程“改名”具有两个参数,第一参数用于指定工作表新名称,第二参数用于指定工作表序号。如果忽略第二参数,则当做1处理。
将三段代码复制到模块中,执行“过程二”。因其第一参数为“总表”,第二参数为2,那么执行结果即工作簿中第二个工作表重命名为“总表”。
而执行“过程三”后,因忽略了第二参数,默认当做1处理,所以结果为第一个工作表重命名为“汇总表”。
Function过程的参数
Function过程的参数与Sub过程的参数在语法上完全一致,可以使用相同的参数。
但是Function过程只能返回引用对象的某个属性值或者运算结果,无法改变对象的属性、格式等等,所以部分带有参数的Sub过程可以直改用Function实现,而部分却无法实现。
Function和Sub一样可以使用一个或者多个参数,也可以使用可选参数。但不同的是Function过程只能返回值,所以在声明Function过程时,其所有参数可以指定数据类型,Function过程本身也可以指定数据类型。例如:
Function Str(rng as range) as string
Function与Sub过程的另一个区别是Sub过程的参数允许与Sub过程名一致,而Function的参数绝不能与Function过程名一致。例如:
Sub 成绩(成绩)
IF 成绩 >= 60 Then MsgBox "及格" Else MsgBox "不及格"
End Sub
Sub Test()
成绩 (59)
End Sub
执行“Test”过程可以正确判断成绩59分是否及格。但若改用Function过程则一定出错:
Function 成绩(成绩)
IF 成绩 >= 60 Then 成绩 = "及格" Else 成绩 = "不及格"
End Function
Sub Test()
MsgBox 成绩(59)
End Sub
执行过程“Test”后将弹出“当前范围内的声明重复”的编译错误。即使再修改为以下方式仍然报错:
Function 成绩(成绩)
IF 成绩 >= 60 Then MsgBox "及格" Else MsgBox "不及格"
End Function
Sub Test()
Call 成绩 (59)
End Sub
正确的方式是:
Function 成绩(分数)
IF 分数 >= 60 Then 成绩 = "及格" Else 成绩 = "不及格"
End Function
Sub Test()
MsgBox 成绩(59)
End Sub
开发自定义函数
节对自定义函数的基础知识做了详解,本节则进行实例演示,通过带有不同参数的函数定义过程来增进读者的理解与编写功底。
对于本节中开发函数中所涉及的各种语法,如条件语句、循环语句、With语句以及常见对象的命令请参阅本书第十章和第十一章。也可以阅读完第十章及十一章后再跟着本节练习函数的开发。
开发不带参数的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单元格则自动产生第二次进库的时间,且第一次进库时间保持不变……具体效果见所示:
〖提示〗:代码中的Now是VBA函数,不是工作表函数NOW(),所以不需要带括号,但它们功能相同。
〖点评〗:相对于系统自带的工作表函数NOW,Nows函数具有不随时间变化的优点,对于记录进库时间这类工作的应用极广。其外,如果仅仅需要不变的日期,忽略时间,可以在不改代码,而直接在公式中套用Text函数即可,例如:
=TEXT(Nows(),"yyyy-m-d")
本例文件参见光盘:..\ 第六章\自定义函数.xlsm
2.取当前工作簿名
〖要求〗:利用函数获取当前工作表名称,不管工作簿是否保存。
〖代码〗:
Function 工作簿名() '获取当前工作簿名称
工作簿名 = 'ActiveWorkbook即表示当前工作簿
End Function
〖测试〗:
进入可工作表,在单元格录入以下公式即可获取当前工作簿名,见图所示:
=工作簿名()
图 测试不随时间变化的时间函数 图 取工作簿名
〖提示〗:未保存的工作簿也具有Name属性,若需要取得路径名,则需要使用FullName属性,不过它需要在保存工作簿后才能取得。
〖点评〗:Excel没有获取工作簿名称的函数,利用Cell函数勉强可以完成,不过它有两个缺点:工作簿名包含路径及工作簿未保存则无法获取名称。本自定义函数不管工作簿是保存都可以顺利地获取工作簿名,不过针对未保存的工作簿就没有后缀名。
开发带有一个参数的Function过程
1.将人民币金额转换为大写
〖要求〗:对于财务报表,金额合度默认为阿拉伯数字,现需将基转换成人民币大写型式。
〖代码〗:
Function 大写(CELL As String) As String '声明函数名,有一个参数
Dim RMBS As String
IF CELL = "" Or Not IsNumeric(CELL) Then 大写 = "": Exit Function '如果参数为空或者非数值则返回空白
IF CELL = 0 Then 大写 = "零元整": Exit Function '如果参数为0则返回“零元整”
'将数值转换成中文大写,并将点替换成“元”,将负号替换成“负”
RMBS = Replace(Replace((Round(CELL, 2), "[DBnum2]"), ".", "元"), "-", "负")
'加入角与分,同时将最后的“零”替换成“元整”
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函数极其相近。
〖测试〗:
如图所示工作表中的员工工资需要汇总后以大写金额显示,那么在单元格B7录入以下公式:
=大写(SUM(B2:B6))
图 汇总并转换成大写
〖点评〗:Excel自带的Text函数可以实现数字转中文大写,但无法实现人民币大写。借用本函数可以大幅提升财务人员的工作效率。大写函数与Text函数在大写方面的差异见下图:
图 Text与大写函数之差异
2.建立工作表目录
〖要求〗:建工作表中建立当前工作簿的目录,且单击工作表名可以跳转到该工作表中。
〖代码〗:
Function 工作表(Optional 序号) As String '声明函数,有一个参数可选参数
'声明为易失性函数
'如果未输入参数,则赋与变量序号为当前表的地址
IF IsMissing(序号) Then 序号 =
IF 序号 > Then '如果参数大于工作表数量
工作表 = "" '返回空
Else '否则
工作表 = Sheets(序号).Name '取表名
End IF
End Function
〖提示〗:
(1)IsMissing用于判断函数的可选参数是否已经传递给过程。在本例中如果不指定函数的参数,则默认返回当前工作表的表名;
(2)Index属性则是指工作表在所有工作表中的序号(从左向右数)。
〖测试〗:
在工作表的A2录入以下公式,并向下填充即可完成工作表目录地创建。当鼠标单击任意单元格A3时,将打开名为“工作簿名”的工作表。
=HYPERLINK("#"&工作表(ROW(A2))&"!A1",工作表(ROW(A1)))
图 创建工作表目录
〖点评〗: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”。
注意:测试此函数会关掉计算机,请在保存所有资料后再行测试。
开发带有两个参数的Function过程
1.对带“/”的数据进行合计
〖要求〗:盘点产品时,部分产品以“双”为单位,部分产品无法配双则以“左/右”形式出现,现需对这种数据进行汇总,且按需求有时会按“只”计算,有时按“双”计算,公式必须具备通用性及可选性。
〖代码〗:
Function hesum(rng As Range, Optional 单双 As Byte = 1) '声明函数,有两个参数,第二个是可选参数
'声明为易失性函数
Dim cell As Range, Sum1, Sum2
For Each cell In rng
IF InStr(cell, "/") > 0 Then '如果有“/”
左 = CLng(Left(cell, InStr(cell, "/") - 1)) '提取/左边的数据
右 = CLng(Replace(cell, 左 & "/", "")) '提取/右边的数据
Sum1 = Sum1 + (左 + 右) '将左右相加
Else
Sum2 = Sum2 + cell * 2 '没有“/”则直接乘以2
End IF
Next
hesum = (Sum2 + Sum1) / 单双 '汇总后除以第二参数
End Function
〖提示〗:
(1)InStr函数用于在盘点表中查找“/”,如果查找结果大于0,则分别取“/”左、右的数值相加,否则直接取数值本身。
(2)函数的第二参数为可选函数,如果忽略参数则按“只”为单位计算,即当做1处理。
测试:如图所示工作表中,在B10单元格录入以下公式可以对盘点数进行汇总,合计数以“只”为单位:
=hesum(B3:B9)
图 汇总表盘表数据
如果需在汇总结果以双为单位,则需要将公式改为:
=hesum(B3:B9,2)
〖点评〗:针对带有“/”的盘点数据,Excel本身不存在可以直接计算的公式,如果用多函数嵌套也可以计算出正确结果,但公式极长,而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))*2
2.中国式排名
〖要求〗:对学生成绩按班级排名,及按性别排名,且需中式排名。
〖代码〗:
Function 排名(区域, 成绩) '声明函数,有两个参数
'声明为易失性函数
Dim Dic As Object, rng, i As Integer '声明变量,包括一个字典对象
Set Dic = CreateObject("") '声明字典对象变量
For Each rng In 区域 '遍历区域
'如果变量rng等于成绩则为变量i赋值1,如果变量rng大于成绩则将rng的值追加到字典中
IF rng = 成绩 Then i = 1 Else IF rng > 成绩 Then Dic(rng * 1) = 1
Next
'如果变量i大于0,即区域中有数据等于成绩,那么排名结果等于字典中的数量加1(字典对象是忽略重复值的)
IF i > 0 Then 排名 = + 1 Else 排名 = "超出范围" '如果成绩与区域中任何不等则返回“超出范围”
End Function
〖提示〗:
(1)CreateObject("")用于创建一个字典对象,它的特点是成员不重复。而中式排名,是需要忽略重复值的。即四人中第一人100分算第一名,两个99分并列第二名,而98分者按第三名计算,而非美式排名中的第四名。所以设计排名函数时需要借助字典这个特性来实现中式排名。
(2)函数的两个参数都支持手动录入参数,而非仅仅限于单元格引用。
〖测试〗:
工作表中有图所示数据,在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单元格,双击单元格的填充柄将公式填充到最末尾即可完成排名计算。
图 按条件排名
〖点评〗:Excel有一个内置函数Rank用于对成绩排名,但它是美式排名法。而更重的是它无法实现按条件排序,它的第一参数必须是单元格,这限制了它的功能发挥,例如以下公式Rank是无法运算的:
=rank(3,{1,2,3,4,5})
而本自定义函数是可使用内存数组作为参数的:
=排名({1,2,3,4,5},3)
开发带有两个可选参数的Function过程
1.获取可控制大小写的英文列标
〖要求〗:返回指定单元格的英文列标,且可以控制列标的大小写状态。如果不指定大小写则默认为大写,如果不指定单元格,则默认计算公式所在单元格的列标。
〖代码〗:
Function col(Optional rng As Range, Optional style As String = "A") '声明函数名称,有两个可选参数
'声明为易失性函数
'如果第二参数录入A和a以外的任意字符则返回空白
IF style <> "A" And style <> "a" Then col = "": Exit Function
IF rng Is Nothing Then Set rng = '如果忽略第一参数则默认取公式所在单元格
'函数结果等于Cells(1, rng)的地址去除后1之后所对应的字母。然后根据第二参数进行大小写控制
col = StrConv(Replace(Cells(1, ).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可以做互补。
2.计算多样式星期
〖要求〗:对指定日期计算星期,有四种格式可选,包括“一”、“星期一”、“Mon”和“Monday”四种。如果未指定日期则以今天为基准,如果未指定格式则以“星期一”这种中文长写为基准。
〖代码〗:
Function 星期(Optional dates As Date, Optional style As Byte = 2) '声明函数名称,具有两个可选参数
IF dates = 0 Then dates = Date '如果忽略第一参数,则以当日计算
'如果仅仅一个参数,则第参数在1到4之间,则将参数值赋与第二参数,而将当前日期赋与第一参数
IF dates < 5 And dates > 1 Then style = dates: dates = Date
Select Case style '根据第二参数值选择星期的格式
Case 1 '第二参数为1
星期 = (dates, "aaa") '短写中文
Case 2
星期 = (dates, "aaaa") '长写中文
Case 3
星期 = (dates, "ddd") '短写英文
Case 4
星期 = (dates, "dddd") '长写英文
End Select
End 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可以实现四种星期格式的运算,但其参数对于新手来说不方记忆。开发自定义函数时需要突破这种弊障,尽量用最简单的参数表示出来;另一个值得学习的是本函数所有参数全是可选的,为用户提供最大的便利。
开发带有不确定参数的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 = (Rng(i), )
For Each cell In celll '遍历单元格区域
Connect = Connect & cell '串连所有单元格字符
Next cell
Case "Variant()" '如果是数组(包括内存数组)
For Each cellv In Rng(i) '遍历数组
'跳过False,将数组中其它元素串连
IF cellv <> False Then Connect = Connect & cellv
Next cellv
Case Else '否则
Connect = Connect & Rng(i) '直接连接(指直接在参数中输入的字符串)
End Select
End IF
Next i
End Function
〖提示〗:不确定参数的函数必须使用ParamArray进行声明参数,使用ParamArray时需要遵循两个规则:
(1)ParamArray所声明的参数必须位于最后位置,即除ParamArray声明的参数外还有其它参数时,该参数必须位于ParamArray声明的参数的左方。
(2)ParamArray所声明的参数必须用Variant数据类型。
(3)Intersect的作用于让函数只计算数据区域与参数所代码区域的重叠区,防止整列、整行或者整个工作表做参为参数造成死机。但它同时也带来了一个缺点:参数只能引用本工作表的区域,引用其它工作表或者工作簿的区域时,将会忽略。
本函数在Excel 2007中具有0到255个参数,而在Excel 2003中则有0到30个参数。每个函数都是可选的。
〖测试〗:
工作表中有图所示数据,为了将工号大于2000的员工的姓名串连成一个字符串,在单元格D2中录入以下数组公式:
= Connect(IF(B2:B10*1>2000,A2:A10))
公式必须用【Ctrl+Shift+Enter】三键组合录入才能得到正确结果。
图 串连内存数组
如果需要串连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 '如果无参数则结果为0
For i = 0 To UBound(rng) '遍历每个参数
IF Not IsMissing(rng(i)) Then '如果有参数
Set celll = (rng(i), )
For Each cell In celll '遍历区域中每个元素
IF Then Fun_tion = Fun_tion + 1 '如果有公式则累加变量
Next cell
End IF
Next i
Functions = Fun_tion '统计结果
End Function
〖提示〗:
(1)在本函数的参数中,Rng是变体型,当做数组处理。那么进入For循环时默认下标为0,不能保使用For I = 1 to UBound(Rng)。如果参数声明为Range对象,那么其下标才是1.
(2)同前一个函数一样,只能对当前表区域统计公式个数。
〖测试〗:
在图所示工作表中,为了统计C列和F列具有多个公式,可以使用以下公式进行计算:
=Functions(C:C,F:F)
公式可以使用1到255个参数,可以还可以累加区域。但是参数引用的区域不可以包含公式所在单元格。
图 统计区域中的公式个数
〖点评〗:工作表函数可以统计空单元格个数、数字个数、文本个数、大于小于某值的个数……本公式用于计算区域中的公式个数,算是对函数功能的补充。
开发具有三个参数其中第三个为可选的Function过程
1.按单元格背景颜色进行条件平均
〖要求〗:按条件对与条件区域同等大小的统计区域计算平均,如果不指定统计区域则以条件区域进行计算。
〖代码〗:
Function AverageIFcol(条件区 As Range, 颜色单元格 As Range, Optional 统计区) '声明函数名称,有三个参数,第三个是可选参数
Dim i As Integer, Counts As Integer, rng As Range, sum As Double '声明变量
'声明为易失性函数
IF IsMissing(统计区) Then Set rng = 条件区 '如果第三参数被忽略,则将条区赋与rng变量
'如果未被忽略,那么以统计区第一个单元格为基准,向下扩充到条件区同等大于的区域赋与变量Rng
IF Not IsMissing(统计区) Then Set rng = 统计区(1).Resize(条件区., 条件区.)
For i = 1 To 条件区.Count '遍历条件区
'如果条件区中某个单元格背景色与颜色单元格区域(参照区)颜色一致,那么
IF 条件区(i). = 颜色单元格(1). Then
sum = sum + rng(i).Value '累加符合条件的数据
Counts = Counts + 1 '统计符合条件的个数
End IF
Next i
AverageIFcol = sum / Counts '最后结果等于总和除以个数
End Function
〖提示〗:
(1)Rng是一个中间变量,用它来替代实际统计区。当有第三参数时则等于第三参数,但参照条件区的大小;当忽略第三参数时则等于第一参数。
(2)为了体现通用性,计算单元格的背景色时必须使用Color,而不能用ColorIndex,否则在Excel 2003中可以使用,在2007却无法正常使用。
〖测试〗:
对于图中的数据,对背景是黄色的学生的成绩计算平均。可用以下公式:
=AVERAGEifcol(K9:K17,K10,L9)
如果条件区和实际统计区是一个区域,可以忽略第三参数,见图所示:
图 按颜色条件对统计区求平均 图 按颜色条件对条件区求平均
〖点评〗:Excel本身有条件求和函数——SUMIF,但无法与单元格颜色做为参照。本函数可以做为SUMIF函数的补充,它与SUMIF函数的用法一致。
2.按颜色从左向向右查找所有数据
〖要求〗:根据参照颜色对查找区域最左列查找同颜色的单元格,然后返回其右边若干列的数据。如果找到多个符合条件的数据,需要全部罗列出来。
〖代码〗:
'声明函数名称,有三个参数,第三个是可选参数,函数的结果是数组
Function VlookupCol(查找值 As Range, 查找区域 As Range, Optional 列数 As Byte = 2) As Variant
Dim Col As Long, cell As Range, arr(), i As Byte '声明变量
'声明为易失性函数
Col = 查找值. '获取参照单元格的背景色
'遍历查找区域的最左边一列
For Each cell In 查找区域(1).Resize(查找区域., 1)
IF = Col Then '如果与参照颜色一致
i = i + 1 '累加变量
ReDim Preserve arr(1 To i) '重新声明数据大小,且保持数组原数据
arr(i) = (0, 列数 - 1) '将找到的单元格右边对应的数值赋与数组
End IF
Next cell
VlookupCol = (arr) '将数组的结果赋与函数
End Function
〖提示〗:
(1)Resize属性用于调整指定区域的大小。在本例中因需要取得查找区域的最左边一列,所以需要借助Resize来重置区域,将行限定为原区域行数,将列限定为1。
(2)因每找到一个目标就需要重置数组Arr的大小,且重置时需要保留原数组的值,所以要循环中必须加入“ReDim Preserve”来声明数组。
(3)Arr数组是横向数组,本例中利用工作表函数Transpose将它转置为纵向数组,再赋与函数。
(4)函数的结果是数组,如果以普通公式录入可以取得第一个查到的目标;以区域数组形式录入也可以返回所有查到的结果,假设存在多个符合条件的目标值的话。
〖测试〗:
在图所示工作表中,A列的姓名以不同背景颜色进行区分,在E1单元格有需要查找的参照颜色,在E2单元格录入以下普通公式可以返回第一个查到的目标数值41:
=VlookupCol(E1,A2:B11,2)
如果需在将符合条件的所有数据全部罗列出来,则需要使用区域数组公式。选择E2:E11区域并录入以下数组公式:
=VlookupCol(E1,A2:B11,2)
必须按【Ctrl+Shift+Enter】三键结束可以才得到正确结果,见图所示。
图 按颜色获取第一个目标值 图 按颜色获取所有目标值
因为无法确定有多少个符合条件的值,那么使用区域数组公式时无法把握好区域大小,即既要将所有结果显示出来,又不能出现错误值“#N/A”,那么可以套用Index来完成。
=IFERROR(INDEX(VlookupCol(E$1,A$2:B$11,2),ROW(A1)),"")
在F1录入公式后,将公式向下填充即可。
图 利用普通公式返回所有结果并排除错值
〖点评〗:本函数可以实现按颜色进行查找,是Vlookup函数的补充。另外对于会用vlookup函数的读者一定有一个心得:vlookup只能返回一个符合条件的目标值。使用其它函数与vlookup嵌套后可以实现查找所有目樯值,但公式很长,也不利于大众理解。所以在自己开发函数时,应尽量完善,且多一些可选项,让终端用户感觉实用且灵活。
编写函数帮助
用户定义的函数不管自己使用还是给其它用户使用,都有必要对函数的功能和参数添加一个说明,使用户在使用上更方便。不仅如此,还有必要对函数进行分类,例如大写函数应该划入财务函数类,那么通过插入函数的向导可以从财务函数下拉列表中找到该函数。
如果打开插入函数向导,可以发现所有自定义函数默认存在于“用户定义”类别中,见图所示。
图 用户定义类别中的自定义函数列表
而双击进入“函数参数”对话框后可以发现,函数的功能没有相应的说明,每个参数的说明全是空白。见图所示。
图 函数参数对话框
为了解决这两个问题,需要简单的定义函数的帮助信息。
VBA中用于指定函数说明的是方法。
方法的基本语法是:
(Macro, Description, HasMenu, MenuText, HasShortcutKey, ShortcutKey, Category, StatusBar, HelpContextID, HelpFile)
各参数的函数见表6-4所示:
表6-4 MacroOptions参数说明
名称
必选/可选
描述
Macro
可选
用户定义函数 (UDF) 的名称
Description
可选
函数的描述
HasMenu
可选
忽略该参数
MenuText
可选
忽略该参数
HasShortcutKey
可选
如果为 True,则为宏指定一个快捷键;如果该参数为 False,则不为宏指定快捷键
ShortcutKey
可选
如果参数为True,则该参数为必选参数;否则忽略该参数快捷键
Category
可选
一个指定现有的宏函数类别的整数,以确定映射为内置类别的整数,还可指定自定义类别的字符串。如果提供了一个字符串,它将作为类别名称显示在“插入函数”对话框中,如果此类别名称从未使用过,则将用该名称定义一个新的类别,如果使用的类别名称与某个内置名称相同,则 Excel 会将用户定义的函数映射为此内置类别
StatusBar
可选
宏的状态栏文本
HelpContextID
可选
一个指定分配给宏的帮助主题上下文 ID 的整数
HelpFile
可选
包含 HelpContextId 定义的帮助主题的帮助文件名
其中Category表示函数的类型。用户可以将自定义函数添加到属于自己的独有类型中,例如:
Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:="andy专用"
以上代码用于将“大写”函数加入到名为“andy专用”的新类别中,见图所示。
图 将函数加入新类别中
当然也可以使用内置的函数类别,例如“财务”、“文本”、“逻辑”等等。在代码中,可以直接使用类别名,也可以使用其常数值。值与类别的对应关系见表6-5所示:
表6-5内置函数类别
值
类别
值
类别
1
财务
8
逻辑
2
日期与时间
9
信息
3
数学与三角函数
10
命令
4
统计
11
自定义
5
查找与引用
12
宏控件
6
数据库
13
DDE/外部
7
文本
14
用户定义
根据上表的分析,如查需要将自定义函数加入“文本”类,可以用以下语句:
Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:=7
Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写", Category:="文本"
以上两句代码的效果一致,都可以将“大写”函数加入到内置的函数类别“文本”中。
MacroOptions方法仅仅对自定义函数生效,内置数函的任何说明性文字和分类都无法修改。
使用MacroOptions方法添加函数的帮助分为两类:普通工作簿和加载宏。基于普通工作簿与加载工作簿的特性不同,在设置函数说明时需要区别对待。
1.普通工作簿
当普通工作簿文件中有自定义函数时,例如“大写”函数,利用以下一个自启动的程序加添加函数说明,即每次加载工作簿时执行。
(1)如果当前工程中有“大写”自定义函数,那么在VBE界面是单击【插入】\【模块】;
(2)在模块中录入以下自启动程序代码,该过程可以在工作簿每次开启时全自动执行:
Sub auto_open()
Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写:仅需要一个参数,单元格引用。" + Chr(10) + "例如:“=大写(a1)”", Category:="财务"
End Sub
(3)保存代码后退出Excel,再重新打开簿;
(4)打开“插入函数”向导,在“或选择类型”下拉列表中选择“财务”,“大写”函数即位于该类别中,见图所示:
图 财务函数中的“大写”函数
(5)双击“大写”函数打开“函数参数”对话框 ,在对话框中有关于“大写”函数的功能、参数说明及用法,见图所示:
图 自定义函数的参数说明
以后不管任何时间打开该工作簿,都可以在插入函数的向导中看到关于“大写”函数的信息,方便用户使用。
2.加载宏工作簿
前一种方法如果用在加载宏(xla格式或者xlam格式)文件中,在开启该加载宏时将会产生以下错误提示:
图 加载宏中添加函数说明时的错误提示
这是由于VBA的规则决定的:在加载宏文件(加载宏的工作簿处于隐藏状态)中,不可以利用代码编辑宏。而控制文件是否具有加载宏属性的方法是改变其IsAddin属性。
所以方法一的代码需要修改为:
Sub auto_open() '每次开启工作簿时执行
With Application
.ScreenUpdating = False '关闭屏幕更新,防止闪屏
= False '显示加载宏工作簿
'添加函数说明
.MacroOptions Macro:="大写", Description:="将阿拉伯数字转为人民币金额大写:仅需要一个参数,单元格引用。" + Chr(10) + "例如:“=大写(a1)”", Category:="财务"
= True '还原为加载宏
End With
End Sub
修改后的代码中,利用“IsAddin = False”使加载宏工作簿由隐藏状态还原为显示状态,然后再借用MacroOptions方法设置函数的说明信息,最后恢复其隐藏状态。
然而利用IsAddin控制其状态切换将带来两个负面作用:
(1)在将工作簿显示状态进行切换时,将会闪屏一次,给用户不舒服的感觉。所以代码中的“ScreenUpdating = False”则用于解决这个问题;
(2)因MacroOptions方法修改了工作簿中宏的属性,在退出Excel程序时,每次都会弹出一个图所示对话框。程序会询问用户否保存加载宏,这是一个令人极其无法接受的事。
图 退出Excel程序时弹出的对话框
为了杜绝这个问题,必须在工作簿的关闭事件中加入以下代码:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
(False)
End Sub
代码的含义是每次关闭当前工作簿时都不保存,从而屏弊提示框。
虽然改为不用工作簿关闭事件,而直接在“auto_open”过程加入一句保存工作簿的代码也可以解决这个问题,但它将浪费一些不必要的时间。
提示:本例文件参见光盘:..\ 第六章\为函数添加说明.xlam
除以上两种办法外,还有一种更简单的方式——从立即窗口执行MacroOptions方法。
使用立即窗口是基于一个前提条件:EXCEL会记录并保存MacroOptions的操作。也可就说只要第一次利用MacroOptions方法设计好后,函数中的说明就永远存在,从而避免每次启动都调用代码。
具体的步骤如下:
(1)打开带有函数的工作簿,使用快捷键【Alt+F11】进入VBE界面;
(2)双击Thisworkbook打开工作簿事件代码窗口,使用快捷键【Ctrl+G】显示立即窗口;
(3)在立即窗口中输入以下代码:
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 Byte
IF * 1 <= 11 Then counts = 30 Else counts = 255
= False
Macro:="我的自定义函数", Description:="本函数具有" & counts & "个参数,其中......", Category:="财务"
= True
End Sub
总结
Function过程即自定义函数,根据工作需要可以开发自己专用的函数。
对于函数的运算速度,内部集成的工作表函数一定快于用户自己定义的函数,在内部函数能够完成的情况下尽量使用工作表函数;如果工作表函数无法完成,或者需要非常长的数组公式才能完成的情况,可以开发专用的自定义函数。
开发自定义函数时,应该尽量给用户一些可选项,使函数运用更简单,公式也更简短。例如内部函数Left,取左边第一个字符时可以忽略第二参数。
对于某些运算结果,如果工作中常见格式较多,应尽可能将所有格式全部罗列出来,让用户选择。例如本章定义的星期函数,支持四种显示方式。
对于有多个结果的函数,应将函数声明为数组,将所有结果显示给用户。但为了体现灵活性,同时需要指定一种默认显示的值。例如自定义函数VlookupCol,相对内部函数Vlookup在某些方面具有独特的优越性。
为了让用户定义的函数在任何工作簿中都可以应用,应该工作簿保存为加载宏文件,并对其加载或者置于自启动文件夹。对于加载宏文件的生成方法参见本书第30章。
最后,为了让用户更快掌握开发者定义函数的功能,尽对函数编写说明,且对其归类。
第七章
VBA的对象模型与对象表示法
VBA语言的绝大部分过程其实就是处理各种对象的过程。只有深入理解Excel VBA涉及的不同对象及其层次结构,才能轻松驾驭VBA语言对对象的控制。
本章主要了解VBA中对象的结构及其表示方法。
本章要点:
VBA中的对象及结构
对象的表示法
单元格的各种引用方式
VBA中的对象及结构
很多语言都集成了VBA环境,可以利用VBA来控制它们。而各种程序的VBA语言在语法上是相通的,仅仅需要处理的对象不同。如果熟悉VBA基本语法,再学其它程序的VBA,那么只需要去了解它具有什么对象就行了。这就是举一反三,也是VBA这类面向对象的语言的特点。同时也显露出对象在VBA的重要性。
关于对象的相关概念
在Excel VBA中,工作簿、工作表、单元格、名称、批注、条件格式、图形、图表、透视表、区域等等都是对象。对象代表了应用程序中的每个元素。
在VBE中可以利用对象浏览器来了解VBA支持哪些对象。单击快捷键【F2】即可弹出对象浏览器,在“工程/库”下拉列表中选择“Excel“,在左边的“类”列表中则列出了Excel VBA所支持的所有对象。而右边则是所选择对象的成员,包括其方法与属性。
图 使用对象浏览器查看Excel对象
对象是一个实体的物件,它具有一些特征,也具有读取和改变这些特征的行为,而且在改变其部分特征时,会自动产生某些预定的反应。在VBA中,我们将这些特征称之为对象的属性,改变特征的行为即为方法,而改变属性时引发的反应即为对象的事件。
例如:
Sub 工作表命名()
Sheets("Sheet3").Activate
= "工作表3"
End Sub
在以上代码中,Sheets("Sheet3")代表一个名为“Sheet3”的工作表对象,Activate是一个方法,它可以激法工作表对象,相当于用户左键单击工作表名的操作。此方法改变了应用程序对象Application的ActiveSheet属性。而第二句代码的Name则是工作表的名称属性,该属性可以读写,即可以获取该属性值也可以改变该属性。最后,工作表的Activate方法引发了两个事件:当前工作表的Deactivate事件及Sheet3工作表的activate事件。
对象与对象集合
对象是一个应用程序中可操作的元素,对象集合则为一组具有相同性质的对象的集合。
例如工作表“Sheet1”是一个对象,而当所有工作表加起来就是工作表集合。其中工作表通常用Worksheet表示,而工作表集合用Worksheets表示,表示对象的复数形式。再如名称与名称集合:
Name——Names
条件格式与条件格式集合
ormatCondition——FormatConditions
单元格与单元格集合
Cells(行,列) ——Cells
对象与对象集合的另一个显著区别是对象没有Count属性,而对象集合是多个对象的组合体,则具有Count属性。
如果需要引用集合中的某个成员,可以通过其在集合中的位置来引用,该位置是从1到集合中子集总数之间的整数。例如选择工作表集合中第三个工作表,该表名称为“表3”,那么有以下两种方式:
Sheets (3).select
Sheets("表3") select
如果需要同时选择当前工作表所有的3个工作表,可以采用以下两种方式:
Sheets(Array("Sheet1", "Sheet2", "Sheet3")).Select
前一种方法是将所有工作表名组成一个数组,将数组做为Sheets的参数即可代表一个集合,后者则直接引用Sheets集合。
如果有三个工作簿窗口“Book1”、“Book2”和“Book3”,那么激活第二窗口也可以使用以下两种方式:
Windows("Book2").Select
Windows(2). Select
从以上叙述可以正确理解对象与对象集合了,但也有一些特殊的对象需要更多地体会才能厘清头绪。
例如工作表对象,它既属于Sheets集合的成员,也属于Worksheets集合的成员。因为Sheets集合包括了所有工作表(Worksheest)对象成员和所有图表(Chart)对象成员,所以以下代码两种方式都可以生成一个新的工作表:
, , , xlWorksheet
, , , xlWorksheet
而生成一个图表则用以下代码:
, , , xlChart
如果不指定表的类型,那么默认是创建工作表对象。
另个还有一类对象——活动对象,表示当前可操作的对象。当对象有多个时,活动对象总是只有一个,它与其它非活动对象在VBA代码中的表达方式不同,且在外观上也有明显的不同。例如:
活动工作簿:ActiveWorkbook
其它工作簿:Workbooks("Book1")、Workbooks(3)等等
在外观上,活动工作簿的标题颜色更深,而非活动工作簿的标题则偏灰色,处于不可操作状态。如果需要在非活动工作簿中录入数据,则需要单击该窗口,使该工作簿成为活动工作簿。
图 区别活动工作簿窗口
同时,从上图中也可以看出活动单元格与非活动单元格的差异。其中A1是活动对象,其边框与其它单元格边框明显不同,且在行号与列标上都加深颜色以突出显示。
提示:手工操作时对象时,必须先激活该对象;在VBA中则不需要,可以向一个非活动对象发送指令,甚至包括该对象处于隐藏状态。
对象的层次:父对象与子对象
对象是有层次的,上一层称父对象,下一层为子对象。
除了最上层的应用程序对象Excel外,任何对象都有一个或者多个父对象。
父对象可以用Parent来表示。其语法为:
其中expression表示对象。即单元格A1的父对象可以表示为“Range("a1").Parent”。如果需要取得工作表名称,那么可以使用以下代码:
MsgBox Range("a1").
一个更具有实用性的应用是:罗列出当前工作表中具有批注的单元格地址,具体代码为:
Sub 罗列所有批注单元格()
Dim nm As Comment, msg As String '声明变量
For Each nm In '遍历所有批注
msg = msg & Chr(10) & (0, 0) '逐一取出批注的父对象的地址
Next
MsgBox "以下单元格有批注:" & msg '报告结果
End Sub
假设工作表中有图所示数据,其中有三个单元格有批注,执行以上代码后可以罗列出所有批注的父对象——单元格的地址,见图所示。如果本例改用Parent以外的任何方法都会效率更差。
图 工作表中的三个批注 图 有批注的单元格地址
本例文件参见光盘:..\ 第七章\获取批注所在单元格地址.xlsm
Parent也可以重复使用,表示父对象的父对象。例如单元格的对象是工作表,工作表的父对象是工作簿,那么计算当前工作簿的名称可以用以下试获取:
MsgBox
Parent是对象的属性,但同时也代表一个对象——父对象。
认识Excel所有对象
Excel中包含248种对象,由于本书的篇幅限制,不一一罗列出来,读者可以从帮助中查看。方法是:【F1】打开VBA帮助,单击“Excel 2007 开发人员参考”,再单击“Excel 对象模型参考”即可。
根据工作性质的不同,每个程序员在编写代码时所涉及的对象也是不同的。然而常用的对象基本一致,见下表所示:
表7-1 常用对象
对象名称
解释
Workbook
工作簿对象
Window
窗口对象
worksheet
工作表对象
Shape
图形对象
Range
单元表格对象
Name
名称对象
Chart
图表对象
CommandBarPopup
下拉菜单对象
CommandBar
工具栏按钮对象
例如VBA程序中常见的一句代码可能就会涉及以上的多个对象:
Sub 添加矩形框()
IF ("book1").Sheets(6).Range("a1") = "矩形 1" Then
("book1").Sheets(6).(msoShapeRectangle, , , 93#, ).Select
End IF
End Sub
在以上代码中,包括了Excel程序对象、工作簿对象、工作表对象、单元格对象和图形对象。除此之外,菜单对象、工具栏按钮对象等等都是极常用的对象。
对象的表示法
VBA对于对象的表示法有很多很多方式,最多的可以有四种方法表示同一个对象。了解对象的表示方法及如何从多种表达方式中寻找最有效率的方式是程序员必要掌握的知识范畴。
对象的完整指定方式与简写
在VBA中对同一个对象可以有多种表达方式,但如果需要不受环境影响、总是指向正确的目标对象,则需要使用完整的引用。这类似于公式中的相对引用与绝对引用,绝对引用在任何情况下都指向目标单元格,而相对引用则完全受制于鼠标的活动。
所谓完整的引有对象,即在引用对象时将其父目对象以及父对象的父象(如果存在的话)同时录入,对目标对象进行限定,从而实现对象对象的“绝对引用”。
例如当前工作簿“人事资料.xlsm”中有三个工作表,需要引用其中的“总表”下A10单元格,那么可以使用以下代码:
("人事资料表.xlsm").Sheets("总表").Range ("A10")
不管在开启多少工作簿,有多个工作表,以上代码总是指向正确的目标单元格,可以确保程序不出错。
利用定义名称获取对象
每个对象都拥有自己的VBA名称供用户调用,对于部分对象还可以对其重新定义一个新名称,并通过用户定义的名称来获取该对象。
以下常见的对象都支持定义名称:
图片、剪贴、形状(在Excel 2003中称自选图形)、艺术字、文本框、单元格(区域).
图形对象的默认名称
Excel对图形对象默认的命名方式是“图形类形 编号”,例如插入第一张图片,那么其默认名称为“图片 1”,插入第三张图片则命名为“图片 3”……见图所示。
如果插入3个表单控制的按钮,则按钮的名称也分别命名为“按钮 1”、“按钮 2”、“按钮 3”,见图所示:
图 图片对象的默认名称 图 按钮的默认名称
在VBA中是可以利用这个默认名称来引用对象的。例如将“按钮 2”的宽度调整为“按钮 1”的宽度的2倍,可以使用以下代码:
Sheets(1).Shapes("按钮 2").Width = Sheets(1).Shapes("按钮 1").Width * 2
或者删除“图片 1”:
Sheets(1).Shapes("图片 1").Delete
2.重定义图形对象的名称
所有图形对象都可以手工定义其名称。方法是选择图形对象后在名称框中录入新的名称,然后单击回车键。重新定义名称的好处是可以将图形名称变得更有意义,也便于识别。
例如将图中的“图片 1”命名为“WIN XP”,那么可以选择该图片后在名称框中录入新名称“WIN XP”再回车。重命名后的效果见图所示:
图 为图形对象定义新名称
此时可以使用新名称来引用它。例如获取名为“WIN XP”的图片所覆盖的区域地址,可以使用以下代码:
Sub 获取覆盖区域()
MsgBox Range(Sheets(1).Shapes("WIN XP").BottomRightCell, Sheets(1).Shapes("WIN XP").TopLeftCell).Address(0, 0)
End Sub
当然,也可以使用代码对图形命名,直接修改其Name属性即可。例如:
Sheets(1).Shapes("图片 1").Name = "AA"
注意:在引用或者命名图片时,特别需要注意空格的半角与全角状态。Excel中默认对图形对象的命名中间是半角空格,如果使用全角空格是无法引用该对象的。
利用集合索引号获取对象
除最顶层的Excel应用程序外,所有对象都处于一个对象集合中,那么就可以利用集合中的索引号来调用对象。
索用号用于对象集合,例如图形对象集合中的第二个子对象可以使用以下方式调用:
Shapes(2)
工作表集合中的第10个工作表可以用以下代码调用:
Sheets(10)
一个具有实用性的案例如下:
Sub 统一宽度()
Dim i As Byte
For i = 1 To Sheets(1).
Sheets(1).Shapes(i).Width = 100
Next i
End Sub
以上代码用于将工作表中所有图形对象统一宽度为100。“For i = 1 To Sheets(1).”表示从第一个开始到最后一个图形对象逐一设置宽度。
或许读者可能思维跳跃,想寻找一个更简便的方式来完成。例如:
Sheets(1). = 200
然而事实证明这种方式无法成功,必须借助DrawingObjects来实现。例如
Sheets(1). = 200
另一个具有实用性的案例如下:
Sub 反向勾选复选框()
Dim i As Byte
For i = 1 To Sheets(1).
IF Sheets(1).CheckBoxes(i) = 1 Then
Sheets(1).CheckBoxes(i) = 0
Else
Sheets(1).CheckBoxes(i).Value = 1
End IF
Next i
End Sub
以上代码将工作表中所有复选框的状态进行反选,即被选中的复选框取消选择,而未选择的则勾选。执行前后效果见图和所示。
图 产品质量表 图 反选复选框
活动对象的简化引用
前面的对象引用方式可以确保总能正确的引用对象,然而却是牺牲效率来实现的。当所引用的对象是当前活动对象时VBA提供了简化的引用方式。
在英文中,active的含义是活动的,现役的,而在VBA它正好用于表示当前已激活的对象。例如:
活动工作表——ActiveSheet
活动单元格——ActiveCell
活动工作簿——ActiveWorkbook
活动窗口——ActiveWindow
在引用活动对象时是不需要罗列其上层对象的,而是直接使用“ActiveCell”或者“ActiveWorkbook”等等。
如图中引用B4单元格的值,可以使用以下两种方法:
ActiveCell
Workbooks("业务报表.xlsm").Sheets("生产表").Range("B4")
图 业务报表
前者在写法上较后者更简洁,而且在执行效率上也有大大的优势。因为VBA在引用对象时按点来处理的,父对象层级越多执行效率越差。
另一点需要特别指出的是,ActiveCell 是Application对象的成员,所以不能使用以下两种方法引用活动单元格:
Sheets(1).ActiveCell
但是中的Application却可以忽略。而且Application对象的很多很多成员都可以忽略Application而直接引用。例如:
利用WITH语句简化对象引用
With语句的功能是代码减肥,且加快快程序执行速度。不过它的前提条件是同一对象需要多次引用时。
Excel的录制宏也同一对象多次引用时,也存在自动调用With语句简化对象引用的智能。例如录入一个宏,对当前选区添加黄色背景色,录制宏产生的代码如下(后面的注释为笔者添加):
Sub Macro1()
With
.Pattern = xlSolid '填充图案
.PatternColorIndex = xlAutomatic '图案的颜色
.Color = 65535 '背景色
.TintAndShade = 0 '颜色深浅度
.PatternTintAndShade = 0 '对象的淡色和底纹图案
End With
End Sub
如果在Excel 2003中则产生以下代码:
Sub Macro1()
With
.ColorIndex = 36
.Pattern = xlSolid
End With
End Sub
从以上两段代码都以看出,录制宏时,如果同一对象连续多次调用时可以使用With来简化。如果第一段代码不使用With,将其还原后将是:
Sub Macro1()
= xlSolid '填充图案
= xlAutomatic '图案的颜色
= 65535 '背景色
= 0 '颜色深浅度
= 0 '对象的淡色和底纹图案
End Sub
或许这种方式编写的代码更利于初学者理解,然而追求执行效率才是开发者们的首选。在程序开发中,只要同一对象超过两次引用,一定要借助With语句来简化。
事件中的Me关键字
Me 关键字是隐含声明的变量。它通常用于事件过程中,表示当前过程的对象。
关于对象的事件在第8章中将详细解说,此处仅需了解Me关键字所代表的对象即可。
如果在工作表事件中使用Me,则它代表当前工作表;如果在工作簿事件中使用Me,则它代表工作簿;如果在窗体中使用Me,则它代表窗体对象。
例如进入工作表“Sheet2”时报告工作表中有多少个OEL对象(ActiveX控件),那么可以使用以下步骤完成:
(1)在工作表标签中的“Sheet2”中单击右键,从弹出的菜单中选择【查看代码】;
(2)在当前代码窗口中录入以下代码:
Private Sub Worksheet_Activate()
MsgBox "当前表OLE对象有:" &
End Sub
(3)返回工作表界面,单击进入工作表“Sheet1”,再次单击“Sheet2”工作表从而激活代码,VBA程序立即弹出对话框,表示当前表中OLE对象的数量,见图所示。
图 报告“Sheet2”OLE对象数量
在上面的代码中Me关键字代表“Sheet2”工作表对象。
本例文件参见光盘:..\ 第七章\ ME关键字使用.xlsm
或许读者会认为“”完全可以简写为“”,那么关键字“Me”有何存在价值?
事实上它有两个作用:作为过程函数调用和提供成员列表
1.作为过程函数调用
仍以图的工作簿为例,现追加一个要求:进入工作表“Sheet1”中报告已用数据区域地址,其操作步骤如下:
(1)在工作表标签中右键单击“Sheet1”,从弹出菜单中选择【查看代码】;
(2)在代码窗口中输入以下代码:
Private Sub Worksheet_Activate()
Call 已用区域(Me)
End Sub
(3)单击菜单【插入】\【模块】,并录入以代码带参数的过程:
Sub 已用区域(Obj As Object)
MsgBox (0, 0)
End Sub
(4)返回工作表界面,通过单击工作表标签切换工作表。当进入“Sheet1”工作表时,将弹出一个对话框,表示当前工作表已用区域的地址,见图所示:
图 报告Sheet1已用区域地址
Me在窗体中也可以代表窗体本身。
2.提供成员列表
VBA中每个对象都有很多属性、方法等等成员,程序员不该将时间花在记忆所有成员的单词上。在Excel中,只要输入对象名及小圆点,那么VBA会自动列出所有成员,程序员仅仅从中选择即可。而关键字Me本身代表一个对象,那么也同样具这有个功能。
例如在录入前面的代码“”时,如果不记得它的具体单词,仅仅知道前一个字母是“O”,那么可以录入“Me.”即可,VBA会自动列出其所有成员列表,见图所示:
图 利用Me获取成员列表
在实际工作中,使用Me关键时需要注意以下事件:
(1)关键字Me不能出现在标准模块中,因为标准模块不能代表对象。若从类模块中复制代码,则必须用指定对象或窗体名来取代Me,以保持原来的引用。通常只能在事件过程中使用,包括工作表代码窗口、工作簿代码窗口和类模块中。
(2)关键字Me不能出现在Set赋值号的左边。例如:
Set Me = MyObject
而正确的对Me赋值是使用Let或者见忽略,例如:
Let Me = MyObject
单元格的各种引用方式
Excel中有很多对象,最常用的对象当属单元格对象,它是数据最基本的载体。而VBA中对单元格的表示方法也较其它对象更多、更复杂,在本小节专门讲述单元格与区域的表示方法。
单元格的最基本的表示方式有三种:Range("a1")方式、cells(1,1)方式和[a1]方式,另外还有交集、合集、偏移量、已用过域、当前区域、End等等单元格引用的相关概念。
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")”语句中则可以获取单元格的值。事实上“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").Address
MsgBox 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来显示这个属性值将得到一个运行时错误,例如图:
图 引用区域默认属性时产生运行时错误
正确的方式是逐个引用区域中单个值。通过索引号做参数来实现。例如:
Range("D2:Z10")(1)——代表D2:Z10区域中第一个单元格的Value,即D2
Range("D2:Z10")(3)——代表D2:Z10区域中第三个单元格的Value,即F2
Range("D2:Z10")(24)——代表D2:Z10区域中第24个单元格的Value,即D3
也就是说,索引号代表区域中从左到右、从上到下的序号,它是区域左上角单元格的参照进行相对引用。
如果索引号为小数时,VBA会自动对其进行四舍五入。如:
MsgBox Range("D3:E7")().Address——结果为“$E$3”,参数当做2处理
MsgBox Range("D3:E7")().Address——结果为“$E$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" & ([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.引用整行、整列
利用“行号:行号”做为参数时可产生对整行的引用,同理利用“列标:列标”做为参数时可产生对整列的引用,如果两个行号或者列标不一致时,可以引用多行或者多列。
以下是一些合法的引用:
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个单元格。
嵌套使用
除以上的四种方法外,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中取最大值10,将这四个字符与冒号串连起来即为“A2:D10”。
Cells(1,1)方式引用单元格
利用Cells引用单元格也有三种用法。
(横座标,纵座标)
引用某工作表中行、列座标所指定的单元格,可以使用本方式,基本语法为:
[Sheet].Cells([RowIndex],[ColumnIndex])——其中工作表对象可选,行与列座标也可选
本方式可以引用某个工作表中横座标与纵座标之交叉点,该座标原点在左上角,向右偏移一个单位即为A列,向下偏移一个单位即第一行,那么Cells(1,1)即为A1单元格。
如果代码中忽略工作表对象,则默认指当前工作表;如果忽略横座标与纵座标号,则默认引用所有单元格。
以下是几种合法的单元格引用:
Sheets(1).Cells(5, 4)——表示引用第一个工作表中行座标为5、列座标为4的单元格D5
Sheets("生产表").Cells(10000, 1000)——表示引用“生产表”中ALL1000。在Excel 2003中该语句将出错,因为其最大列为256
(行号,列标)
本引用方式依靠目标地址的行号与列标来确定目标单元格。其中行号与列标两个参数都是必选参数。而工作表对象Worksheet则是可选参数。
以下三个引用为合法的单元格对象引用:
Sheets("生产表").Cells(2, "C")——表示引用“生产表”中C2单元格
Cells(12, "ZZ")——表示引用当前表ZZ12单元格
Cells("12", "ZZ")——仍然表示ZZ12单元格,即行号并非一定要使用双引号,但列标一定要使用双引号
本方法引用单元格永远只能引用一个单元格,不能引用区域。
(横座标,纵座标)
本方式引用单元格是以其父对象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(, )——表示引用B2:G10区域第二行、第四列G3单元格
还可以使用负数或者0做为参数,那么其座标计算方式则向左与向上偏移。例如:
Range("D4:G10").Cells(-1, -1)——表示引用B2单元格
Range("D4:G10").Cells(0, -2)——表示引用A3单元格
图是负数座标的图示:
图 图示Cells(2, 2) 与Range("B2:G10")的关系 图 图示Range("D4:G10").Cells(-1, -1)
(索引号)
当使用单个索引号做为参数时,它表示父对象中的一个索引子集。其编号方式是先行后列、先左后右。
例如以下的引用:
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超过区域的最大个数后,则继续向其下一行开始累加,直到超出整个工作表的边界
[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三个区域,包括21个单元格
[D2:D3,D5]——表示引用D2:D3和D5两个区域,中间的冒号和逗号允许使用全角,VBA会自动将其转换为半角
而以下引用则是不合法的单元格引用:
["D2:D3"]——参数不能使用引号
[A1:F2500000]——行数超过允许的最大值1048576
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编程时循环语句使用非常频繁,那么使用支持循环的引用方式对于工作具有较大的便利性。现例举一个循环的案例:
Sub 标示补考人员()
= False: Rem 关闭屏幕更新
Dim Row_Count As Byte: Rem 如果成绩超过255个,则不用声明为Byte
For Row_Count = 2 To 101: Rem 遍历所有成员
Rem 如果成绩小于60,则在右边一个单元格显示“需补考”
IF Cells(Row_Count, 2) < 60 Then Cells(Row_Count, 3) = "需补考"
Next: Rem 循环检查下一个
= True: Rem 恢复屏幕更新
End Sub
在以上循环中,Cells的参数使用变量从而实现所有成绩单元格的引用,如果[A1]方式引用成绩则无法完成。
鉴于Range ("A1")方式也可进行行的循环,那么此程序也可以改为以下形式:
Sub 标示补考人员2(): Rem Range方式
= False: Rem 关闭屏幕更新
Dim Row_Count As Byte: Rem 如果成绩超过255个,则不用声明为Byte
For Row_Count = 2 To 101: Rem 遍历所有成员
Rem 如果成绩小于60,则在右边一个单元格显示“需补考”
IF Range("B" & Row_Count) < 60 Then Range("C" & Row_Count) = "需补考"
Next: Rem 循环检查下一个
= True: Rem 恢复屏幕更新
End Sub
本例文件参见光盘:..\ 第七章\ Cells循环演示.xlsm
Selection与ActiveCell:当前选区与活动单元格
在选择了单元格或者区域的情况下,Selection可以引用选择区域所有单元格,通常简为选区。而ActiveCell则表示活动单元格。选区可以是一个单元格,也可以包含多个单元格甚至多个区域,但活动单元格仅仅一个,而活动单元格一定包含于选区中。
如果选区仅仅一个单元格时,那么选区与活动单元格完全相同,它们代表相同的对象。
如果选区包括多个单元格时,那么活动单元格的确定方式为:最先选择的单元格为活动单元格。如果首选择A1,然后通过拖动鼠标继续A1:D3,那么第一个选择的单元格A1为活动单元格;如果选择D3,然后通过拖动鼠标继续A1:D3,此时选区与前者一致,但活动单元格却为D3。
在外观上,选区与活动单元格也有区别。选择呈灰色显示,而活动单元格则背景为白色。Excel 2003中这种反差较明显,2007中差异较小,但仍然可以利测探颜色进行识别。图即为Excel 2003中的选区与活动单元格,而图即为Excel 2007中的选区与活动单元格。
图 Excel 2003selection与ActiveCell 图 Excel 2007selection与ActiveCell
如果需要选区不变的情况下改变活动单元格,那么可以用两种方法完成:
(1)单击法
按住Ctrl键后鼠标单击选中任何单元格,该单元格即可活动单元格
(2)VBA激活法
利用VBA可以改变当前活动单元格,方法是利用Activate语句。其语法如下
其中Range表示待激法的单元格,只能包含一个单元格,如超过一个则会产生运行时错误。
如果需要对选区中第四个单元格激活,那么使用以下语句:
Selection(4).Activate
如果需要对选择中第二列第三行单元格激活,则用以下语句:
(3, 2).Activate
如果需将最后一个单元格激活则用以下语句:
Selection().Activate
提示:Activecell和Selectiion只能用于当前工作表,如果加前置对象来指定其它工作表或者工作簿,将产生运行时错误。例如:
Sheets(2).Selection——不管当前表是否Sheets(2)都产出错
Names:利用名称引用单元格或区域
单千分表或者区域都可以手工定义为更有意义的名称,在代码中引用该名称比引用区域地址更具有可读性。
例如以两句代码:
[b9].FormulaArray = "=SUM(a1:a8*b1:b8)" '汇总A1:A8的数量与B列的单价之积
[b9].FormulaArray = "=SUM(数量*单价)" '汇总A1:A8的数量与B列的单价之积
A1:A8存放产品的数量,B1:B8存放产品的单价,那么VBA中使用“SUM(数量*单价)”显示比“SUM(a1:a8*b1:b8)”更利于阅读者理解。
1.将区域转换成名称
在使用名称前,需要将区域定义一个新名称。一义名称可以使用Application对象的Names 属性。其具体语法
(Name, RefersTo, Visible, MacroType, ShortcutKey, Category, NameLocal, RefersToLocal, CategoryLocal, RefersToR1C1, RefersToR1C1Local)
的参数较多,但其中最常用的第一、二个参数,其作参数通常忽略。
假如需要对图中的各列数据进行命名,Name为各列的标题,RefersTo为标题以外的数据区域,那么根可以得到以下过程代码:
Sub 添加名称()
[a1], [a2:a7]
[b1], [b2:b7]
[c1], [c2:c7]
End Sub
定义名称后,可以进入名称进管理器查看名称(Excel 2007才有名称管理器,Excel 2003不存在,不过如果读者学习完本书后,完全可以为Excel 2003开发一个类似于2007自带的管理器),在名称管理器中将罗列出刚才定义的所有名称,见图所示。
图 待定义名称的工作表数据 图 名称管理器
VBA还提供了一种批量定义名称的方法:Range对象的CreateNames 方法,其具体语法如下:
表达式.CreateNames(Top, Left, Bottom, Right)
各参数的含义如下:
表7-3 CreateNames参数详解
名称
必选/可选
描述
Top
可选
如果该值为 True,则使用顶部行中的标签创建名称。默认值为 False
Left
可选
如果该值为 True,则使用左侧列中的标签创建名称。默认值为 False
Bottom
可选
如果该值为 True,则使用底部行中的标签创建名称。默认值为 False
Right
可选
如果该值为 True,则使用右侧列中的标签创建名称。默认值为 False
本方法仅仅用于对单元格区域批量定义名称,而方法可以对区域定义名称外,还可以对常量、公式定义名称。
根据表7-3的参数解释,可以编写出以下过程代码来取代方法。代码如下:
Sub 批量定义名称()
Range("A1:C7").CreateNames Top:=True, Left:=False
End Sub
此方式定义名称的结果与前面的代码执行结果完全一致,而且更为高效。
本例文件参见光盘:..\ 第七章\将区域转换为名称.xlsm
2.在过程中引用名称
引用区域名称的语法为:
Range ("Name")
即利用名用户定义的名称做为Range的参数,且需要在名称外添加双引号。
针对前面建立的名称,如果需要计算第三个产品的价值,那么利用区域引用和使用名称引用就有以下两种引用方式,读者可以从中观察两种方法的差异。
Sub 计算第三种产品价值()
MsgBox "产品:" & Range("A2:A7")(3) & "的价值为:" & Range("B2:B7")(3) * Range("C2:C7")(3)
End Sub
注意:“Range("A2:A7")(3)”不能写作“[A2:A](3)”。
Sub 计算第三种产品价值2()
MsgBox "产品:" & Range("产品名称")(3) & "的价值为:" & Range("数量")(3) * Range("单价")(3)
End Sub
本过程将名称代入过程中取代单元格地址,结果完全相同,但对于阅读者来说,更直观、易懂。
UsedRange与CurrentRegion
表示工作表中已用区域,即用户已经使用过的单元格区域。
例如工作表“Sheet1”中A1:V2存放了数据,其经区域一直保持空白,那么A1:V2区域是该表的已使用区。如果A1:V2之外的B10也录入一个新数据,那么该表的已用区域即为A1:V10。
一个工作表只有一个已用区域,它将工作表中使用过的最小行行号与最小列列标定义为已用区域的左上角单元格地址,再将工作表中使用过的最大行行号与最五列列标定义为已用区域的右下角单元格地址,最终组合并已矩形的已用区域。也可以换一种说法:可以包斜含已所有已经使用过的单元格的最小区域,即为工作表的已用区域。
可以使用图来演示已用区域范围。
图 工作表的已用区域Usedrange
从上图中可以看到,A1:F10或者A1:Z10000等等都包含了已经使用过了的所有单元格,但其中最的区域是B2:E6,那么B2:E6即为工作表的已用区域Usedrange。
在使用Usedrange时需要注意一个重点:Usedrange判断单元格是否“已用”,不按单元格是否“非空”做为标准,而是“非空且无特殊格式”。如果单元格空白,但对单元格设置了看不见的加粗、倾斜、下划线之类格式,那么该单元格也属于已用区域Usedrange之一。例如在图中,单击F7单元格,再按下快捷键【Ctrl+I】,那么该表已用区域即相对成为B2:F7。
利用以下语句可以取得工作表的已用区域地址:
MsgBox (0, 0)
提示:如果工作表中所有单元格都未曾使用,那么UsedRange代表A1.
表示当前区域。当前区域是以空行与空列的组合边界,且包括Range对象的区域。
与Usedrange相比较,它们的不同点见7-4所示。
表7-4 已用区域与当前区域之比较
比较对象
比较项目
UsedRange
CurrentRegion
判断标准
非空及是否有格式
非空及是否与Range之间存在空白行、空白列
每个工作表中拥有的数据
1个
多个
上层对象
Worksheet
Range
由此可见,每个单元格都拥有自己的CurrentRegion,如果某单元格非空且与指定对象Range之间不存在空行或者空列间隔,那么它就属于该Range的CurrentRegion成员。
例如在图中,工作表中的已用区域和单元格A1的当前区域获取方法如下:
Sub UsedRange与CurrentRegion()
MsgBox "UsedRange:" & & Chr(10) _
& "CurrentRegion:" & Range("a1")., 64, "提示"
End Sub
其中Chr(10)表示编码中10的字符,即相当于回键符,可以将字符串换行。
图 成绩表 图 UsedRange与CurrentRegion
但是在大多数情况下,和是一致的,它们代表完全相同的区域。即工作表中只有一个CurrentRegion时,任意两个已用元格之间都不存在空行与空列间隔时。
与CurrentRegion应用
实例一:工作表减肥
用Excel制表时间较长的用户可能会有这种体验:工作表中数据不多,但是工作表占用空间很大,这就是所谓的虚胖。它的原因之一是UsedRange中存在着很多带格式的空单元格,利用VBA清除这部分区域就可以对文件进行减肥。
例如在新工作表A1:H20区域录入任意字符,并按下快捷键【Ctrl+B】和【Ctrl+I】对所有数据加粗并倾斜显示,然后选择G:H列,按下键盘上的【Delete】键清除其数据;同样方式清除第11到20行的数据。此时被清除数据有区域,虽然空白,却保留了格式,而使工作表“虚胖”。
解决办法下:
Sub 工作表减肥()
MsgBox "减肥前:" & '获取减肥前的已用区域地址
'清除多余的列
Dim Col_Count As Long
With '使用With简化对象引用
For Col_Count = . To 1 Step -1 '从已用区域最后一列开始,直到第一列
'如果循环中某单元格整列皆为空白
'(Resize方法的作用是使用For只在已用区域的第一行循环,忽略其它行,提升代码效率)
IF (.Item(1).Resize(1, .)(Col_Count).EntireColumn) = 0 Then
'将该列删除
.Item(1).Resize(1, .)(Col_Count).
Else '否则
Exit For '只要该列有一个数据则退出循环,避免删除必要的空列
End IF
Next
End With
'清除多余的行
Dim Row_Count As Long
With '使用With简化对象引用
For Row_Count = . To 1 Step -1 '从已用区域最后一行开始,直到第一行
'如果循环中某单元格整行皆为空白
'(Resize方法的作用是使用For只在已用区域的第一列循环,忽略其它列,提升代码效率)
IF (.Item(1).Resize(., 1)(Row_Count).EntireRow) = 0 Then
'将该列删除
.Item(1).Resize(., 1)(Row_Count).
Else '否则
Exit For '只要该列有一个数据则退出循环,避免删除必要的空列
End IF
Next
End With
endd:
MsgBox "减肥后:" & '报告减肥后的已用区域地址
End Sub
执行以上代码,将弹出两次对话框,表示减肥前后的已用区域地址,分别为:
图 减肥前已用区域地址 图 减肥后已用区域地址
本过程的思路是:从已用区域最后一列向前逐列检查,如果某列为空白,则整列删除;然后从已用区域最后一行向上逐行检查,如果某行为空白,则整行删除。
本例文件参见光盘:..\ 第七章\工作表减肥.xlsm
实例二:学生座次交换
为了不影响学生的视力,学校后定期将每组学生的座位逐一交换。本例即为学生进行座次交换。图是某班四个组别的人员座次表,现要求通过程序交换四个组的位置,每执行一次交换一次,即ABCD交换为BCDA、CDAB等等。而且需要确保程序的通用性,即添加、删除组别时,不需要修程序,仍然可以正常执行。
图 某班四个组别座次表
根据上图的数据,代码编写如下:
Sub 数据交换()
Dim rng As Range, adds As String, i As Byte, j As Byte, rngg
'获取所有姓名所在地址
For Each rng In (1).Resize(1, )
IF rng = "姓名" Then adds = adds & (0, 0) & ","
Next rng
With Range(Left(adds, Len(adds) - 1))
'统计组别个数
j = .
'将最后一个区域的值存入内存中
rngg = .Areas(1).CurrentRegion
For i = 1 To j - 1 '遍历最后一个区或以外的所有区域
'将下一个区域的值赋于当于区域
.Areas(i).CurrentRegion = .Areas(i + 1).
Next i
.Areas(j).CurrentRegion = rngg '再将内存中的值赋于最后一个区域
End With
End Sub
代码中“Range(Left(adds, Len(adds) - 1))”所引用对象Range(“A1,D1,F1,G1”)是一个多区域对象,即多个区域组成,而非单个区域。如果通过Cells或者Item来获取其子集的话,将只能从取出第一个区域中的子集。例如“Range("A1,D1,F1,G1").Item(2).Address”结果为“$A$2”,而非“$D$1”。为了避免这个问题,必须使用Areas来取其子集。
在本过程中,CurrentRegion作用为将Range("A1,D1,F1,G1")对象中每个子集扩展为该子集的当前区域。例如将A1扩展为A1:B5,将D1扩展为D1:E5……
在例中CurrentRegion的作用无可取代。
本例文件参见光盘:..\ 第七章\交换座次.xlsm
SpecialCells:按条件引用区域
Range对象的SpecialCells方法可以返回一代表与指定类型和值匹配的区域。它的具体参数为:
表达式.SpecialCells(Type, Value)
其中“表达式”表示单元格对象,“Type”参数表未单元格类型,“Value”参数是可选参数,只在“Type”参数为xlCellTypeConstants 或 xlCellTypeFormulas时用,用于确定结果中应包含哪几类单元格。
其中Type可以包含以两上种类型的参数:XlCellType 常量和XlSpecialCellsValue 常量。关于XlCellType常量和XlSpecialCellsValue常量可以从下表查看其可用值:
表7-5 XlCellType常量可用值详解
XlCellType 常量
含义
值
xlCellTypeAllFormatConditions
任意格式单元格
-4172
xlCellTypeAllValidation
含有验证条件的单元格
-4174
xlCellTypeBlanks
空单元格
4
xlCellTypeComments
含有注释的单元格
-4144
xlCellTypeConstants
含有常量的单元格
2
xlCellTypeFormulas
含有公式的单元格
-4123
xlCellTypeLastCell
已用区域中的最后一个单元格
11
xlCellTypeSameFormatConditions
含有相同格式的单元格
-4173
xlCellTypeSameValidation
含有相同验证条件的单元格
-4175
xlCellTypeVisible
所有可见单元格
12
表7-6 XlSpecialCellsValue 常量可用值详解
XlSpecialCellsValue 常量
值
xlErrors
16
xlLogical
4
xlNumbers
1
xlTextValues
2
根据表7-5,用引Range ("A1:G10")区域中空单元格可以使用以下代码:
Range("A1:G10").SpecialCells(xlCellTypeBlanks)
引用Range("A1:G10")区域中带有批注的单元格可以使用以下代码:
Range("A1:G10").SpecialCells(xlCellTypeComments).Delete
而引用所有带有公式的单元格则用以下代码:
Range("A1:G10").SpecialCells(xlCellTypeFormulas, 23)
如果SpecialCells的参数指定的单元格类型不存在,那么引用结果并非Nothing,而会直接弹出错误提示。例如在一个不存公式的区域中引用所有公式区域:
Sub 公式所在区域()
MsgBox TypeName(Range("A1:E14").SpecialCells(xlCellTypeConstants, -4123))
End Sub
执行以上代码后,结果为一个运行时错误,而非Nothing。为了解决这个问题,需要利用IF函数配合防错手法来完成。例如修改后的代码为:
Sub 公式所在区域()
On Error Resume Next '出错时继续执行
Dim rng As Range '声明对象变量
'将引用对象赋值变量
Set rng = Range("A1:E14").SpecialCells(xlCellTypeConstants, -4123)
IF > 0 Then '如果有错误
MsgBox "未找到带有公式的单元格", 64, "提示" '弹出提示
Else '否则
'选择所有公式所在单元格
End IF
End Sub
提示:SpecialCells方法引用单元格可以通过录制“定位条件”的宏来完成,即在录制宏时可以产生关于SpecialCells的所有代码,用户在不熟悉参数用法的时候可以通过录制宏来获取代码。“条件定位”对话框见图所示。
图 SpecialCells方法对应的“定位条件”对话框
CurrentArray:引用数组区域
数组公式是用于建立可生成多个结果或可对在行和列中排列的一组参数进行运算的单个公式。而数组区域是包含同一个数组公式的单元格集合。
数组区域有两个显著特点:
(1)公式两端有花扩号“{}”
数组公式包括两种,单元格数数组公式和区域数组公式。它们都有相同的外观特征:公式两端自动产生花扩号“{}”。图即可典型的数组公式
(2)无法单独编辑其中某个单元格
区域数组公式跨越多个相邻的单元格,用户无法单独编辑其中任何单元格,包括修改、删除等等。如果需要修改区域数组公式,需要选择整个数组区域。图为编辑数组区域中单个单元格时的出错提示:
图 典形的数组公式与数组区域 图 编辑数组区域中单个公式时出错
数组区域可以利用CurrentArray来引用,其具体语法为:
Range可以是数组区域中的任意单元格。例如图中,单元格A1的数组区域和A2的数组区域完全一致。
如果Range不要数组区域中,则引用将产生错误。
下面通过两个案例讲解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 = '将数组区域赋与变量
IF = 0 Then Arrays = '如果不存在错误则将数组区域转换成值
'清除错误设置
Next rng
End Sub
本例文件参见光盘:..\ 第七章\转换数组区域的公式为值.xlsm
Resize:重置区域大小
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)
Range([a2], [c10]).Resize(4, 5)——表示A2:E5,将原区域列数增大,行数减小
[A:A].Resize(1, 16384)——表示第一行,将原有的整列转置为整行,仅仅2007可用
Resize的参数可以使用小数,VBA会将其进行四舍五入进行转换成整数。但是参数不可使用负数和0,否则将产生运行时错误。
[A4:B7].Resize(, )——表示A4:B5,重置为3行2列
也可以利用表达式做为Resize的参数,例如:
[A4:B7].Resize(1, [A1] + 5)——重置为1行,A1的值加上5列
[A4:B7].Resize(([c:c]), ([a1:C2]))——重置后的行数与列数由C列之和及a1:C2区域最小值决定
根据以上的参数解说,可以对Resize方法有了较深认识。下面举两个案例以拓宽读者的思路:
实例一:复制Sheet2已用区域到Sheet1表的A1
Sub 复制工作表已用区域()
'利用With简化对象引用
With Sheets(2).UsedRange
'将Sheet1的A1单元格重置为Sheet2已用区域的大小,然后再将其赋值为Sheet2中已用区域的值
Sheets(1).[a1].Resize(., .) = .Value
End With
End Sub
在对一个区域的赋与另一个区域时,需要确保两个区域的高度和宽度一致,否则将产生错误。而利用Resize做可以计算数据源的大小,再将目标区域重置为相同大小,那么赋值就不再有问题。
实例二:隔一行插行N行
假设在工作表前20行实现隔一行插入N行,而N需要由用户指定,那么以下代码完全可以实现:
Sub 隔一行插入N行()
= 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). Shift:=xlDown '插入行
Next i
[a1].Resize(Row_Count * 1, 1). '删除第一行前插入的行
= True '恢复屏幕更新
End Sub
执行以上代码时将弹出一个对话框,让用户指定行数,默认值为1,见图所示;
如果录入2,并执行程序,那么数秒钟后在工作表中每行数据前都将产生两个空行,见图所示:
图 指定插入行数 图 插入新行的状态
Offset:根据偏移量引用区域
Range对象的Offset属性可以返回一个Range对象,代表位于指定单元格区域的一定的偏移量位置上的区域。其具体语法如下:
(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:E4
Range("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(, )——表示引用F4单元格,参数当进位为1计算,而舍位为2计算
还可以使用表达式做参数,列如:
Range("F2").Offset(([C:C]), [a1] + 5)——目标区域由C列数据和及A1的值决定
Offset在实际工作中应用极广,现举三个案例应用。
实例一:仍然是交换座次,但相对于中的实例要求上有一点不同:每组的行标题改为组名。在交换座次时,不能移动任何组别名称。
图 座次表
本例需要使用Offset来完成,完整代码如下:
Sub 数据交换() 'Offset应用
Dim rng As Range, adds As String, i As Byte, j As Byte, rngg
'获取所有姓名所在地址
For Each rng In (1, )
IF Len(rng) > 0 Then adds = adds & (0, 0) & ","
Next rng
With Range(Left(adds, Len(adds) - 1))
'统计组别个数
j = .
'将最后一个区域的值存入内存中,Offset的作用是向下偏移一行,从而避免移动标题
rngg = .Areas(1).(1)
For i = 1 To j - 1 '遍历最后一个区或以外的所有区域
'将下一个区域的值赋于当于区域
.Areas(i).(1) = .Areas(i + 1).(1).Value
Next i
.Areas(j).(1) = rngg '再将内存中的值赋于最后一个区域
End With
End Sub
本例文件参见光盘:..\ 第七章\交换座次(Offset).xlsm
实例二:对任何数据选区进行列合计。例如图,如果选择数据区域B2:E10,那么程序就对该对该区域的各行汇总,再对各列汇总,汇总结果放在F2:F10及B11:F11区域。同时需要体现程序的通用性,即不管选择任何区域都可以实现对该对域的数值进行行与列的汇总。
利用Offset可以有效地处理本类问题,具体代码如下:
Sub 行列自动合计()
'先汇总各行的值
For i = 1 To '从1到总行数
'利用Offset取得汇总数据的放置位置,即选区第一个单元格向右偏移选区的列数
'合计区域也用Offset逐行偏量来获取,Resize的作用是重置为1行,否则会汇总其它行的数据
Selection(1).Offset(i - 1, ) = ((i - 1).Resize(1))
Next
'再汇总各列的值
For i = 1 To + 1 '从1到总列数加1,因为需要对行的汇总数再进行汇总
Selection(1).Offset(, i - 1) = ((, i - 1).Resize(, 1))
Next
End Sub
首先选择数据区或B2:E10,然后执行程序,计算结果见图所示:
图 等汇总的生产数据 图 行列汇总结果
该程序以选区为基准,所以使用前需要选择正确的区域。
实例三:复制sheet2中的数据到当前表空白区
当前表已使用部分区域,现要求对Sheet2中2行标题以外的数据复制到当前表空白区。利用Offset可以完成,代码如下:
Sub 复制数据()
With 'With减少对象的引用次数
'利用Offset取得当前表已用区域之后第一个空白单元格,配合Resize将区域重置为与Sheet2标题以外的数据一样大小
'然后将两个相同大小的区域直接赋值即可。但在赋值时需要注意一个问题:Value不能省略
(1, 1).Offset().Resize(. - 2, .) = .Offset(2, 0).Resize(. - 2, .).Value
End With
= xlContinuous '对已用区域添加边框
End Sub
Offset在很多地方都与Resize配合使用,可以随心所欲重置区域大小与位置。如果单用Resize,它的基点总固定在区域左上角位置,而单独使用Offset,则不管偏到什么位置,其高度和宽度受原区域限制,而两者搭配却可以突破所有限制。
本例文件参见光盘:..\ 第七章\复制标题以外的数据到当前表非空区.xlsm
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)
其中前个两个为必选参数,后面28个为可选参数。其返回结果为单元格对象。
例如同时引用A2:B2和D3:G4两个区域,那么可以利用Union方法合并两个区域,然后再引用该新区域即可。方法如下:
([A2:B2], [D3:G4])
如果读者还记得前面所介绍的Range引用多区域的方法,那么可能会认为不使用Union方法仍然可以引用多区域的合并区域。例如以上区域可以表示为:
Range("A2:B2,D3:G4")
然而,Range参数的字符限制使它在多区域应用方面无法取代Union,当程序长度超过256个字符时必将产生编译错误。而Union方法则可以突破这个屏障。
现举两个应用案例体现Union在工作中的用途与优势。
案例一:选择当前表中所有产量超过100的单元格,数据见图:
图 机台产量数据
首先利用循环获取所有目标单元格地址,代码如下:
Sub 选择大于100的单元格()
Dim rng As Range, msg As String '声明变量
For Each rng In Range("B2:I13") '遍历整个数据区域
'如果大于100则取出其地址,并与逗号串连
IF > 100 Then msg = msg & (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则取出其地址,并与逗号串连
IF > 100 Then '如果对象变量rng大于100
IF Rang Is Nothing Then '如果变量rang还没有初始化
Set Rang = rng '将符合条件的第一个目标赋与变量Rang
Else '否则
'合并两个对象(已查找到的区域与当前符合条件的单元格合并为一个区域)
Set Rang = Union(Rang, rng)
End IF
End IF
Next rng '判断下一个
'选择合并区域,即所有符合条件的单元格
End Sub
本例文件参见光盘:..\ 第七章\选择大于100的所有单元格.xlsm
案例二:隔行着色
对1到20行中隔行添加背景色,代码如下:
Sub 隔行着色()
Dim rng As Range, rngg As Range '声明变量
For Each rng In Range("A1:A20") '遍历A1:A20
IF Mod 2 = 1 Then '如果行号是奇数
IF rngg Is Nothing Then '如果Rngg未初台化
Set rngg = '将对象变量Rng的整行赋与变量Rngg
Else '否则
Set rngg = (, rngg) '将变量Rng与Rngg合并
End IF
End IF
Next rng
= 17 '添加背景色
End Sub
代码中两个重点:
(1)利用Mod函数判断行号是否奇数
(2)对奇数行合并为一个区域,最后对该区域着色。合并后权需着色一次,否则需要着色10次。这是Union函数的优势。
本例文件参见光盘:..\ 第七章\隔行着色.xlsm
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个为可选参数。
相对于合集,区或的交集在工作中应用更广,常常利用它来提升代码执行效率。
以下为几种常见的交集引用方式:
([a1:a10], [2:2])——表示引用单元格A2,A1:A10与第二行的交界处是A2,通过图可以观察什么是交集:
图 两个区域的交集
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 As Range, Cancel As Boolean)
'首先将DE列和G列合并为一个区域,然后将其与当前活动单元格进行判断,如果重叠就退出程序
IF Intersect(Union([D:E], [g:g]), Target(1)) Is Nothing Then Exit Sub
'活动单元格中输入日期
Target(1) = Date
End Sub
将以上代码录入ThisWorkbook代码窗口中,然后返回工作表,在D列、E列或者G列任意单元格双击即可返回当前系统日期,而不影响其它单元格。
代码中Union的作用是将三列合并为一个区域,再与当前单元格进行比较,如果重复就允许双击录入日期,否则退出程序。
本例文件参见光盘:..\ 第七章\双击输入日期.xlsm
案例二:将工作表中已区域的标题填充底纹,将数据区添加边框
图是生产表数据,现需对其行标题及列标题添加灰色底纹,对数据区域添加边框。代码需要通用,即工作表中添加、删除行或者列后都可以完全适用,不需要修改任何代码。
图 待设置底纹与边框的生产表
利用Offset和Union来完成,代码如下:
Sub 设置边框与底纹()
Dim rng As Range
Set rng = '将已用区域赋与变量,变量的使用目录是减化代码的长度
'对已用区域向下偏移一行,以及向右偏移一列,再将两个区域的重叠区域设置边框
((1, 0), (0, 1)). = xlContinuous
'对已区域和A列的重叠区域添加底纹
(rng, [a:a]). = 15
'对已区域和1行的重叠区域添加底纹
(rng, [1:1]). = 15
End Sub
本例中多次使用Intersect引用交集,从而避免引用多余的区域。因为Offset在进行偏移时,将新区域保持原区域大小,而实际待操作区域大小稍有差异,那么利用Intersect来引用多区域的交集就可以排除多余的区域。
当然,如果读者对Resize的功能比较熟,本例也可以改用Resize来改变区域大小,从而实现Intersect类似的功能。
本例文件参见光盘:..\ 第七章\为生产表添加边框与底纹.xlsm
案例三:统计选区中与A1背景色一致的单元格个数
工作表的数据区域有多种颜色,现需计算与A1单元格颜色一致的单元格个数。代码如下:
Sub 计算选区中背景色等于A1背景色的单元格个数()
Dim rng As Range, colors As Long, Item As Long
colors = Range("A1"). '获取A1单元格的颜色(一个Long型数值)
'在选区及已用区域的交集中循环
'目的是防止用户选择太多的区域时浪费程序时间
For Each rng In (, Selection)
'如果某单元格的颜色与A1颜色一致则累加计数器
IF = colors Then Item = Item + 1
Next rng
'报告计数器
MsgBox "共有:" & Item & "个", 64, "颜色统计"
End Sub
代码中使用Color而不用ColorIndex是因为ColorIndex仅仅能表示0到56,可以适用于Excel2003的颜色数量,而2007的颜色远远超过56种,使用ColorIndex来表示颜色会产生错误,仅仅多种不同种颜色的单元格的ColorIndex完全相同。为了避免这个错误及兼容Excel 2003,必须用Color获取单元格颜色值。
本过程中Intersect的作用是将循环区域限制在已用区域中,以防止用户全选或者选择整列时共费不必要的时间。因为For循环是在用户的选区中一直进行比较,直到最后一个单元格,不管单元格是否空白。即合整工作表只有两个单元格已用,当选择整列再执行时,仍然需要循环1048576次,而事实上必要的循环只要两次。而Intersect的作用正是弥补这种缺憾。
在自定义函数中有时也需要使用Intersect来减少循环次数,避免在非使用区域中循环计算而浪费时间。
本例文件参见光盘:..\ 第七章\颜色统计.xlsm
End:引用源区域的区域尾端的单元格
如果活动单元格在一个较大的数据区域中间,按下快捷键【Ctrl+上箭头】、【Ctrl+下箭头】、【Ctrl+左箭头】、【Ctrl+右箭头】可以迅速将当前单元格移至已用区域的边缘;如果整个工作表均为空白则可以在最小行、最小列或者最大行、最大列之间切换;如果在空白区向数区域使用同方向的快捷键,则可以定位于该行(列)的第一个或者最后一个非空单元格。
Range对象的End属性正好对应于以上四个快捷键,可以实现最上端、最下端、最左端和最右端的切换。它的具体语法为:
(Direction)
其中必选参数Direction表示所要移至的方向,有四个常量,见7-7所示:
表7-7 End四个参数详解
名称
值
描述
xlDown
-4121
向下
xlToLeft
-4159
向左
xlToRight
-4161
向右
xlUp
-4162
向上
以下语句配合图示或者可以更清晰、明了地理解End属性的用法与技巧。所有代码都基于图进行演示,如果工作表中数据有变化,则代码可能无法得到正确结果。
(1)引用C列第一个非空单元格,代码如下:
Range("C1").End(xlDown)——从空白单元格C1向下,遇到第一个非空单元时停止
Range("C4").End(xlUp)——从非空区域中间向上移,遇到最后一个非空单元格时停止
Range("C1048576").End(xlUp). End(xlUp)——双C列最后一个非空单元格向上,遇到第一个非空单元格时停止,再次向上,遇到最后一个非空单元格时停止。
在本例中三种方法都可以得到正确结果,然而三种方法都有缺陷:
如果C1单元非空,即整个数据区域包括了C1时,那么第一种方法会出错;
如果C4与C列第一个非空单元格之间有一个空单元格间隔,那么第二种方法会出错;
如果C4与C列第一个非空单元格之间有两个空单元格间隔,那么第三种方法会出错。
图 测试数据
那么如何才能获取C列第一个非空单元格且代码通用呢?重点在于借用IF函数判断C1是否非空。完整代码如下:
Sub 获取C列第一个非空单元格地址()
Dim msg As String
IF Len([c1]) > 1 Then '如果C1非空
msg = "C1" '变量Msg赋值为C1
Else '否则
msg = Range("C1").End(xlDown).Address(0, 0) '赋值为C1向下第一个非空单元格
End IF
MsgBox msg '报告地址
End Sub
代码思路为:利用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, )) > 1 Then '如果第四行最末列非空
msg = Cells(4, ).Address(0, 0) '变量Msg赋值为第四行最末列的地址
Else '否则
msg = Cells(4, ).End(xlToLeft).Address(0, 0) '赋值为第四行最末列向左第一个非空单元格
End IF
MsgBox msg '报告地址
End Sub
从以上代码中,可以了解到End属性获取最后一个非空单元格地址的方法,还可以学习在代码中引用最末行或者最末列时如何让代码兼容Excel 2003和Excel 2007。
鉴于代码的通用性问题,“Cells(, 1)”是A列最后一个单元格的最精典引用方式。
另外有一个问题值得注意,如果工作表中第4行为空行,那么语句“Cells(4, ).End(xlToLeft)”将引用A4,即无法找到非空单元格时,即引用第四行第一个单元格。
(3)获取E列已用区域下面第一个空白行的行号
Cells(, 4).End(xlUp).Row + 1
代码中的Row表示获取单元格的行号。本代码对于Excel 2003和Excel 2007都通用。至于另一个关于最后一行非空的问题基本上可以不用考虑,因为用户不可能将工作表中65536行(Excel 2003)或者1048576行(Excel 2007)都存放数据。
本例文件参见光盘:..\ 第七章\ End属性演示.xlsm
下面再通过两个案例演示End属性在工作中的具体应用,来促使读者对End有进一步认识。
案例一:将当前表以外的所有工作表都合并到当前表
图 成绩表与汇总表
工作簿中第一个表为“汇总表”,其它工作表分别为各班级的成绩表,班级个数不确定。现需将所有班级的数据全部复制到“汇总表”中,并按先后顺序存放。代码如下:
Sub 合并三个班成绩到总表() '必须当前表是汇总表时执行
Dim sht As Worksheet '声明变量
For Each sht In Sheets '遍历所有工作表
IF <> Then '如果sht的名字不等于当前表名字
'如果工作表A列非空(本程序要求工作表的数据必须从A列开始存放)
IF (sht.[a:a]) > 0 Then
'将工作表sht中A1到最后一个非空行之间的所有行复制到当前表的从上到下第一个空行
sht.[a1].Resize((, 1).End(xlUp).Row, ).Copy _
(, 1).End(xlUp).Offset(Len([a1]) > 0, 0)
End IF
End IF
Next sht '复制下一个
End Sub
本代码在合并各表的数据时,具有以下三个智能:
首先,不管当前哪一个表是活动工作表,不影响成绩合并,总能将成绩数据复制到“汇总表”;
其次,如果成绩表A列为空白,则忽略该表;
最后,每次复制数据到汇总表中时都会自动追加到第一个空白行,使不覆盖、丢失数据的同时,所在数据刚好整齐排列,不产生空行。
本例如果使用Usedrange获取每个成绩表的数据,然后合并可以使用代码更简短。然而UsedRange只能在正常情况下使用,若工作表中有假空行(删除数据却保留了格式),那么复制数据时将会把空行也复制这来。而使用End(xlup)则完全可以避过这个问题,它以单元格是否有数据为标准进行判断,而忽略格式。所以本例采用End(xlup)来提升代码的通用性。
本例文件参见光盘:..\ 第七章\合并所有班级成绩到总表.xlsm
案例二:录入数据时自动定位下一行第一个空单元格
工作表各行中已录入的数据个数不同,现要求每次录入数据后自动定位于下一行第一个空单元格,以提高入速度。
实现以上需求可以利用工作表事件来完成(对于事件的详解请参阅本书第八章),代码如下:
'声明工作表事件
Private Sub Worksheet_Change(ByVal Target As Range)
'如果只在一个单元格中编辑数据就执行事件过程
IF = 1 Then
'使用Cells参数是为了兼容Excel 2003, + 1则表示下一行
With Cells( + 1, ).End(xlToLeft)
'自动选择下一行第一个非空单元格
.Offset(0, -(Len(.Text) > 0)).Select
End With
End IF
End Sub
将以上代码录入在工作表Sheet1的事件代码窗口中,然后返回工作表,在D2单元格录入数据后回车,Excel会立即定位于第三行第一个空单元格C3;而C3录入数据后则会自动定位于第四行第一行空单元格B4……
图 送货表
本例文件参见光盘:..\ 第七章\自动定位下一行第一个空单元格.xlsm
单元格引用的方式非常非常多,也远比其它对象更复杂,读者需要多多练习,并多多阅读他人的VBA代码实例,从中学习、借鉴对象引用的技巧。
第八章
自动宏与Excel事件
Excel VBA具有很多智能,程序自动化执行即为其中一种。
利用VBA的事件可以使代码在不同的条件下自动执行,且该触发条件有近百种。本章针对自动宏及Excel事件的分类及触发条件等等将会做详细地解说,促使读者对Excel的事件驾驭得更娴熟。
本章要点:
让宏自动执行
详谈VBA的事件
VBA有哪些事件
让宏自动执行
早在Excel 5中就出现了自动宏,它可以让程序自动化。而后续版本中则引进事件来进一步强化程序的自动执行能力。
Auto自动宏
在VBA中,只要将宏的名称命名为“Auto_Open”,且保存在模块中,那么开启工作簿时就可以自动执行该宏程序。
对应的,如果宏程序的名称命名为“Auto_Close”,且代码保存在模块中,那么在关闭工作簿时也可以自动执行该宏程序。
例如每次开启工作簿时让工作簿的状态栏显示“四维实业公司人事报表”,而关闭工作簿时自动保存工作簿。
Sub auto_open()
= "四维实业公司人事报表"
End Sub
Sub auto_close()
End Sub
以上两段代码中,第一段为工作簿打开时设置其状态栏文字为“四维实业公司人事报表”,在工作簿开启时自动执行,见图所示;第二段则可以让工作簿关闭时自动保存,而不用用户手工单击保存。
图 修改报表状态
Auto自动宏给Excel用户来极大的便利。但是在后续的各个版本中,Excel引进了更先进的事件来执行自动宏,意味着Auto_open宏的淘汰。
本例文件参见光盘:..\ 第八章\ Auto自动宏.xlsm
工作簿事件中的自动宏
在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()
= "四维实业公司人事报表"
End Sub
对应Auto_Close宏也有相应的工作簿关闭事件:Workbook_BeforeClose。它可以在关闭工作簿时自动执行。
如果需要达成节中的相同效果,那么可以使用以下代码:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
End Sub
Workbook_BeforeClose事件接受一个参数,该参数用于控制工作簿是否允许用关闭,其详情将在下一节中展示。
如果用户同时写入了Auto宏和Workbook_Open事件程序,它的执行顺序如何呢?先来做一测试:
(1)在Thisworkbook代码窗口录入以下代码:
Private Sub Workbook_Open()
MsgBox "Workbook_Open事件"
End Sub
(2)单击菜单【插入】\【模块】;
(3)在模块中录入以下代码:
Sub Auto_Open()
MsgBox "Auto_Open宏"
End Sub
(4)保存工作簿并重启工作簿,可以发现首先弹出“Workbook_Open事件”,然后是“Auto_Open宏”。
本例文件参见光盘:..\ 第八章\自动宏与事件的顺序.xlsm
利用鼠标移动事件执行自动宏
除了以上代全自动的执行代码外,Excel也支持其它的很多方式来引发代码执行。例如鼠标移过图片是,报告图片的边距。具体实现步骤如下:
(1)进入工作表“Sheet1”,并单击功能区【开发工具】\【插入】\【图像(ActiveX控件)】(图标为:);
(2)在工作表中按下左键并拖动,从而绘出一个图像控件;
(3)对图片控制单击右键,并选择菜单【属性】,从而打开“属性”窗口,见图;
(4)单击“Picture”属性右边的浏览图标,从“加载图片”对话框中选择目标图片并单击“确定”按钮返回工作表,图像控制的效果如果所示:
图 图像控件的属性窗口 图 加载图片后的图像控件
(5)单击功能区中的【设计模式】切换按钮,从而退出设计模式;
(6)右键单击工作表标签中的“Sheet1”,选择菜单【查看代码】打开VBE界面中的“Sheet1”代码窗口;
(7)在代码窗口中录入以下代码:
Private Sub Image1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
MsgBox & ":左边距为" & & " 上边距为" &
End Sub
(8)按下快捷键【Alt+F11】返回工作表,当鼠标移过图片时,将会弹出图所示代码,
图 鼠标移过时报告其边距
以上过程Excel的事件引发程序自动执行,在下一节中,将详细的讲述Excel所支持的各种事件。
本例文件参见光盘:..\ 第八章\鼠动鼠标报告图片边距.xlsm
详谈VBA的事件
在VBA中,工作簿、工作表、窗体、控件、图表等等都支持事件,通过事件可以实现办公自动化。每个VBA爱好者都需要深入地理解事件的分类,及每个事件在引发条件、实现功能,从而提升工作效率。
事件的定义与分类
事件在Excel中执行某个操作时引发预定义的动作。事件过程则是一个事件发生时所引发由用户编写的Sub过程。事件类型无法自定义,但事件发生后引发的动作却可以人为控制。
例如在工作表中有一个Change事件,它表示工作表任意单元格的值改变时所引有的事件,如果用户利用鼠标、键盘或者程序去试图修改工作表中任何单元格,那么就会引发名为Change的事件。用户无添加一个工作表本身没有的事件,如鼠标移过事件,却可以随意定制Change事件触的过程。
Excel支持上百种事件,如果需要进行分类通常按代码的存放位置和事件的级别来区分。表8-1中即7个类别的事件及其代码存放位置列表。
表8-1 事件分类
事件级别
代码存放位置
引发对象
应用程序级
类模块
任何工作簿
工作簿级
Thisworkbook
当前工作簿
工作表级
工作表(Sheet1、Sheet2等等)
当前工作表
图表级
图表(Chart1、Chart2等等)
当前图表
窗体级
UserForm
当前窗体
ActiveX控件
工作表或者窗体中
当前控件
类模块
类模块
用户定义的对象
其中每个级别的事件都包含多个事件。如果需要了解每个事件的含义,可以从帮助文件中获取完整解释。例如需要查看工作表事件,那么只需要查找“Worksheet 对象成员”,Excel的帮助浏览器就会罗列出所有关于工作表事件,见图所示。
图 工作表事件列表
事件的层次与执行顺序
事件是区分层次的,不同层次的事件在执行顺序上有先后之别。
1.事件的层次
Excel的事件具有不同的层次。其中最顶层是应用程序事件,不管任何工作簿都可以引用该事件;其次是工作簿事件,只有当前工作簿才可以触发;最底层是工作表事件,只有在代码所在的工作表才可以触发;而工作表中的ActiveX控件和工作表事件、图表事件属于同一层次。
Excel事件的层次遵循高层次事件包含低层次事件的原则。如应用程序除了具有它自己的事件外,还包括工作簿事件,工作表事件,即某个操作可以触发工作表事件或者工作簿事件时,它同时也可以触发工应用程骗子级别的事件。
而工作簿事件除了自身事件外,也包括其下属的工作表事件。一个可以触发工作表事件的操作同时也可以触发工作簿事件。
例如工作表级别的“Worksheet_Activate”事件,同时会触发工作簿级别的“Workbook_SheetActivate”事件,以及应用程序级别的“Application_SheetActivate”事件。
工作表事件属于最低层的事件,它只自己属于自己的事件。
不同层次的事件过程只能在自己专属于的代码窗口录入代码,否则无法触发对应的事件过程。例如工作表“生产表”的事件“Worksheet_Activate”应该置于“生产表”代码窗口,若置于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 appevents
Sub auto_open()
Set = Application
End Sub
Sub auto_close()
Set = Nothing
End Sub
(7)单击菜单【插入】\【类模块】,并按下快捷键【F4】显示属性窗口,将其默认的名称“类1”修改为“appevents”;
(8)双击进入类模块“appevents”,然后录入以下应用程序级事件过程:
Public WithEvents app As Application
Private Sub app_SheetActivate(ByVal Sh As Object)
MsgBox "已通过应用程序级事件激活“总表”"
End Sub
(9)保存工作簿代码,返回工作表界面,双击工作表名“Sheet1”进入第一个工作表,然后使用快捷键【Alt+F8】打开宏对话框,关在宏名列表中选择执行“Auto_open”;
(10)此时应用程序级别的事件已经可以启动了。单击进入工作表“总表”,此时将依次弹出三个对话框,见图、图和图所示:
图 工作表事件 图 工作簿事件 应用程序事件
通过实例证明,不同级别的事件执行顺序为:工作表级——>工作簿级——应用程序级。
3.同一级别事件的执行顺序
大部分的操作都会触发多个事件,例如活动工作表是“Sheet2”时单击表名“Sheet1”,那么它可以触发“Sheet2”的“Deactivate”事件,和“Sheet1”的“Activate”事件。
也可以采用前面的方法来确定多个同级别事件的执行顺序。例如新建工作表时会触发三个工作簿级别的事件。包括“NewSheet”、“SheetActivate”和“SheetDeactivate”事件。
测试办法是将三个事件的代码都录入到Thisworkbook窗口中,然后执行一个可以触发三个事件的动作。其代码如下:
Private Sub Workbook_NewSheet(ByVal Sh As Object) '创建表时执行
MsgBox "新建工作表:" &
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object) '激法表时执行
MsgBox "获得焦点"
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object) '失去焦点时执行
MsgBox "失去焦点"
End Sub
当前工作簿中新建工作表时,根据提示信息可以得到事件的顺序:
“NewSheet” ——>“SheetActivate” ——>“SheetDeactivate”
对于其它事件,读者也可以利用以上方式进行测试,从而判断同一动作触发的多事件执行顺序。
事件的禁用与启用
Excel的事件可以手工启用和禁用。
在VBA中,通过Application对象的EnableEvents 属性来控制,将EnableEvents属性设置为True即启用事件,设为False即为禁用事件。完整的代码如下:
= True
= False
通常在以下两种情况下需要禁用事件:
临时关闭事件
防止事件进入死循环
1. 临时关闭事件
根据需要,工作簿中可能设置了工作簿事件或者工作表事件,例如自动将录入的任何单词转换成大写。然而某些特殊情况下需要暂时禁止事件,即现在需要小写状态,那么进入VBE中删除代码,等后续需要时再录入代码显然非明智之举。
针对这类需要临时性在禁用事件和允用事件之间切换的需求,可以使用一个专用Sub过程来完成,且对该过程指定一个快捷键,那么在需要禁用事件时按下快捷键即可。
禁用和允用事件的过程如下:
Sub 允用事件()
= True
End Sub
Sub 禁用事件()
= False
End Sub
为过程指定快捷键的方法参见6章第1节。
2. 防止事件进入死循环
死循环也称无限递归,指程序无限次地调用自身。死循环的结果是系统资源耗尽或者产生“溢出堆栈空间”的运行时错误,严重影响正常工作。
例如工作表中使用了Change事件,而且该事件过程会修改工作表中的数据,这个修改又将引发Change事件……如果仅有一个事件,那么通常会调用自身97次后停止,如果有多个事件参与,则可能进入死循环,耗尽系统资源。
例如目的是在工作表中实现“不管录入什么数值都自动加1”,代码如下:
Private Sub Worksheet_Change(ByVal Target As Range)
Target = Target + 1
End Sub
以上代码在思路上完全正确,然而因为递归引起事件的执行结果与我们的原本需求背道而驰。在单元格中录入1,结果会显示98,录入100则显示197……
如果再加入句代码,程序立即进入死循环:
Private Sub Worksheet_Change(ByVal Target As Range)
Target = Target + 1 '将当前单元格追加1
[a1] = Target '将当前单元格的值赋与A1
End Sub
所以为了防止这种负面效应,通常在代码中修改EnableEvents属性来避免。且固定格式为:
Private Sub Worksheet_Change(ByVal Target As Range)
= False
您的代码
......
= True
End Sub
现举一个更具有说服力的实例,假设需要在工作表“生产表”A列中手工录入生产日期,而B列则使用VBA的事件自动产生该日期所对应的星期。按常规思路,代码如下:
Private Sub Worksheet_Change(ByVal Target As Range)
(0, 1) = (Target, "DDDD")
End Sub
当返回工作表在B1录入日期“2009-5-10”后可以发现,本应在B列产生英文星期,结果却从C2到CS2都出现了星期“Saturday”,这是递归产生的副作用。
图 工作表事件引起的递归现象
如果修改EnableEvents属性,合理地禁用、启用事件,那么程序完全可以达到预期效果了。代码如下:
Private Sub Worksheet_Change(ByVal Target As Range)
= False
(0, 1) = (Target, "DDDD yyyy年mm月dd日")
= True
End Sub
本例文件参见光盘:..\ 第八章\ Chenage事件引起的递归.xlsm
事件代码的录入方式
事件过程是一个非常特殊的Sub过程。它的代码可以手工逐字录入,但效率极差且出错机率高。
VBA为程序员提供了一个高效而准确录入代码的便捷方式——借用对象与过程下拉框自动产生事件代码外壳。
假设需要录入工作表级“Worksheet_FollowHyperlink”事件的代码,那么可以参照以下步骤:
(1)从工作表标签处右键单击工作表名称,从菜单中选择【查看代码】;
(2)在代码窗口顶中的“对象”下拉列表中选择“Worksheet”,此时代码窗口会产生以下代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
End Sub
因为“Worksheet_SelectionChange”事件是Excel工作表的默认事件,只要选择对象后它就会自动产对“Worksheet_SelectionChange”事件的外壳。此时忽略它,单击右边的“过程”下拉列表,其中罗列了所有工作表事件的过程,见图所示:
图 从过程下拉列表选择工作表事件过程
(3)当选择“FollowHyperlink”过程名后,代码窗口将自动产生“Worksheet_FollowHyperlink”事件代码的外壳,此时再删除“Worksheet_SelectionChange”事件相关的代码即可。
此方式录入代码时准确度比手工录入代码会高很多,特别是有很多参数的事件过程代码。例如图像控件的鼠标移过事件:
Private Sub Image1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
End Sub
VBA有哪些事件
VBA中有应用程序事件、工作簿事件和工作表事件,还有窗体、控件专用的事件。本节就VBA中的应用程序事件、工作簿事件和工作表事件进行介绍。窗件、控件相关的知识会在本书第15章进行讲述。
应用程序级别事件介绍
应用程序级别的事件是Excel中最高级别的事件,它包括的事件相对其它任何对象的事件都更多,总共30个。见表8-2所示。
表8-2 应用程序级别的事件列表
名称
说明
NewWorkbook
当新建一个工作簿时发生此事件
SheetActivate
当激活任何工作表时发生此事件
SheetBeforeDoubleClick
当双击任何工作表时发生此事件,此事件先于默认的双击操作发生。
SheetBeforeRightClick
右键单击任一工作表时发生此事件,此事件先于默认的右键单击操作
SheetCalculate
在重新计算工作表时或在图表上绘制更改的数据之后发生此事件
SheetChange
当用户或外部链接更改了任何工作表中的单元格时发生此事件
SheetDeactivate
当任何工作表被停用时发生此事件
SheetFollowHyperlink
单击Excel中的任何超链接时发生此事件
SheetPivotTableUpdate
在数据透视表的工作表更新之后发生此事件。
SheetSelectionChange
任一工作表上的选定区域发生更改时,将发生此事件
WindowActivate
工作簿窗口被激活时,将发生此事件
WindowDeactivate
任何工作簿窗口被停用时将发生此事件
WindowResize
任何工作簿窗口调整大小时将发生此事件
WorkbookActivate
当激活任一工作簿时发生此事件
WorkbookAddinInstall
当工作簿作为加载宏安装时,发生此事件
WorkbookAddinUninstall
当任一作为加载宏的工作簿卸载时发生此事件。
WorkbookAfterXmlExport
在Excel保存或导出指定工作簿中的 XML 数据之后发生此事件
WorkbookAfterXmlImport
当刷新现有的 XML 数据连接或新的 XML 数据被导入任一打开的 Excel 工作簿之后,发生此事件
WorkbookBeforeClose
当任一打开的工作簿关闭之前立即发生此事件
WorkbookBeforePrint
在打印任一打开的工作簿之前发生此事件。
WorkbookBeforeSave
在保存任一打开工作簿之前发生此事件
WorkbookBeforeXmlExport
在Excel保存或导出指定工作簿中的 XML 数据之前发生此事件
WorkbookBeforeXmlImport
在刷新现有的 XML 数据连接或新的 XML 数据被导入任一打开的 Excel工作簿之前,发生此事件
WorkbookDeactivate
当打开的工作簿转为非活动状态时发生此事件
WorkbookNewSheet
在任何打开的工作簿中新建工作表时发生此事件
WorkbookOpen
当打开一个工作簿时发生此事件
WorkbookPivotTableCloseConnection
在数据透视表的连接关闭之后发生此事件
WorkbookPivotTableOpenConnection
在数据透视表的连接打开之后发生此事件
WorkbookRowsetComplete
如果用户在 OLAP 数据透视表上深化记录集或调用行集操作,则会发生 WorkbookRowsetComplete 事件
WorkbookSync
当作为“文档工作区”一部分的工作簿的本地副本与服务器上的副本进行同步时,发生此事件
任何工作簿都可以引发应用程序级别事件。包括现在已经开启的工作簿、即将打开的工作簿以及暂时不存在(新建)的工作簿。
应用程序级别的事件相比其它事件比较特殊,Excel没有提供内置的对象来捕获应用程序级别的事件,必须声明一个对象,且将Excel应用程序赋值给该对象,然后在类模块中去设置程序的事件过程。
在本书第12章中将将对应用程序级别的事件进行案例演示。
工作簿事件介绍
工作簿事件是Excel的事件中级别居中的事件,它除了自己的事件外,子对象工作表具有的所有事件也会同时引发工作簿事件。它包括29个事件,见表8-3所示。
表8-3 工作簿级别的事件列表
名称
说明
Activate
激活工作簿、工作表、图表工作表或嵌入式图表时发生此事件
AddinInstall
当工作簿作为加载宏安装时,发生此事件
AddinUninstall
当工作簿作为加载宏卸载时,发生此事件
AfterXmlExport
在 Excel 保存或导出指定工作簿中的 XML 数据之后发生此事件
AfterXmlImport
在刷新现有的 XML 数据连接或将新的 XML 数据导入到指定的 Excel 工作簿之后,发生此事件
BeforeClose
在关闭工作簿之前,先产生此事件。如果该工作簿已经更改过,则本事件在询问用户是否保存更改之前产生
BeforePrint
在打印指定工作簿(或者其中的任何内容)之前,发生此事件。
BeforeSave
保存工作簿之前发生此事件
BeforeXmlExport
在 Excel保存或导出指定工作簿中的 XML 数据之前发生此事件
BeforeXmlImport
在刷新现有的 XML 数据连接或将新的 XML 数据导入到指定的Excel工作簿之前,发生此事件
Deactivate
图表、工作表或工作簿被停用时发生此事件
NewSheet
当在工作簿中新建工作表时发生此事件
Open
打开工作簿时,发生此事件
PivotTableCloseConnection
数据透视表关闭与其数据源的连接后发生此事件
PivotTableOpenConnection
数据透视表打开与其数据源的连接后发生此事件
RowsetComplete
如果用户在 OLAP 数据透视表上深化记录集或调用行集操作,则会引发此事件
SheetActivate
当激活任何工作表时发生此事件
SheetBeforeDoubleClick
当双击任何工作表时发生此事件,此事件先于默认的双击操作发生
SheetBeforeRightClick
右键单击任一工作表时发生此事件,此事件先于默认的右键单击操作
SheetCalculate
在重新计算工作表时或在图表上绘制更改的数据之后发生此事件
SheetChange
当用户或外部链接更改了任何工作表中的单元格时发生此事件
SheetDeactivate
当任何工作表被停用时发生此事件
SheetFollowHyperlink
单击Excel 中的任何超链接时发生此事件
SheetPivotTableUpdate
在数据透视表的工作表更新之后发生此事件
SheetSelectionChange
任一工作表上的选定区域发生更改时,将发生此事件
Sync
当作为“文档工作区”一部分的工作表的本地副本与服务器上的副本进行同步时,发生此事件
WindowActivate
工作簿窗口被激活时,将发生此事件
WindowDeactivate
任何工作簿窗口被停用时将发生此事件
WindowResize
任何工作簿窗口调整大小时将发生此事件
工作簿事件级别低于应用程序的事件,只有当前工作簿才可以触发工作簿中的事件代码。
工作簿事件主要包括三个大类:
工作簿开启、关闭时引发事件
工作簿中任意一个工作表、透视表引发事件
工作簿窗口的变化引发事件
在工作中常用的是前两类,在本书第12章中将对工作簿事件进行案例演示。
工作表事件介绍
工作表事件是最底层的事件,它只有9个事件,见表8-4所示。
表8-4 工作表级别的事件列表
名称
说明
Activate
激活工作簿、工作表、图表工作表或嵌入式图表时发生此事件
BeforeDoubleClick
当双击工作表时发生此事件,此事件先于默认的双击操作
BeforeRightClick
右键单击工作表时发生此事件,此事件先于默认的右键单击操作
Calculate
对于 Worksheet 对象,在对工作表进行重新计算之后发生此事件
Change
当用户更改工作表中的单元格,或外部链接引起单元格的更改时发生此事件
Deactivate
图表、工作表或工作簿被停用时发生此事件
FollowHyperlink
当单击工作表上的任意超链接时,发生此事件
PivotTableUpdate
工作簿中的数据透视表更新后发生此事件
SelectionChange
当工作表上的选定区域发生改变时发生此事件
只能存放代码的工作表才可以触发工作表事件,如果需要事件过程对任何工作表都生效,请用工作簿事件。
在使用工作表事件时需要特别注意的是要防止事件过程进入无限递归。
在本书第12章中将将对工作表事件进行案例演示。
事件的特例
所谓特例,是指按照常规思路,大家认为可能触发事件的操作,它却没有触发事件;而大家认为可能不会触发事件的动作,事实却引起了事件过程的执行。
现例举六个这类特例:
1.删除空文本时触发“Worksheet_Change”事件
“Worksheet_Change”事件是指工作表中任意单元格的内容发生改变时所触发的工作表事件。然而在A1为空白的状态下按下键上的【Delete】键也可以执行该事件。例如:
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox "您在修改:" & (0, 0)
End Sub
当用户删除本身为空白的A1时,将会弹出图所示提示信息:
图 删除空单元格触发“Worksheet_Change”事件
2.插入批注时不触发任何事件
任何单元格插入批注时都不触发任何事件,这一点需要记住。
可以理解为批注就是一个悬浮于单元格之上的图形对象,导入图形不改变单元格数据。
3.修格单元格格式不触发事件
不管对单元格设置任何格式,包括背景色、填充图案、对齐方式及边框,定义数字格式等等都不触发任何事件。
虽然定义数字格式时单元格的显示内容发生了改变,然后单元格的文字并未变化,即Range的Text属性未被用户修改。
4.清除格式会触发“Worksheet_Change”事件
更为怪异的是清除单元格格式时会触发“Worksheet_Change”事件。
我们无法清楚微软当初的设计意图,为什么清除格式这种不修改单元格Text属性的操作会触发工作表事件,只要记住这个特例即可。
5.数据分列时不触发事件
假设A1单元格中的文本字符串为“中国/广东/广州”,将其按分割符“/”进行分列,见图所示。在分列时不触发任何事件,包括“Worksheet_Change”事件和“Worksheet_SelectionChange”事件。
图数据分列
6.表单控件修改数据时不触发任何事件
表单控件中的复选框、列表框和组合框等等都可以修改单元格的值,但是经过多次测试它不会触发任何事件。
但是ActiveX控件中的复选框、列表框和组合框却可以触发工作表事件、工作簿事件和应用程序事件。所以如果需要工作表事件能够监视控件修改单元格的值,应该使用ActiveX控件,而不是表单控件。
第九章
VBA程序常规则
在编写VBA程序时需要遵循很多规则,才可以更有效率地开发程序,才能让他人更清晰地看明白代码的设计意图与思路,才可能方便自己修改代码……
这些规则都不是强制性的,甚至都是不成文的,只是一些先驱们的经验积累,到最后就成为不成文的默认规则。遵循这些规则,对于一个程序员来说至关重要。
本章要点:
代码编写规则
优化代码
代码编写规则
本节介绍在代码编写过程的中一些常规则。
对代码添加注释
不管程序开发人员和终端用户是否同一个人,都完全有必要对代码进行注释。即开发的代码属于自用型或者转给其他用户使用,都需要让代码具有清晰的含义注释与思路注释,才有利于阅读和修改。
1.添加注释的作途
添加代码注释主要有三个作用:
每句代码的含义注释
说明当前代码的含义,让阅读者明白代码思路。
整个程序的声明注释
通常对于大中型插件,需要声明开发者姓名、版权、版本号、开发日期、更新日期、更新内容及本程序功能说明、注意事项等等,有利于用户了解各版本功能及差异及使用时的注意事项。
调试代码
在编写代码时,需要对每句代码进行多次调试,直到完全满意才会投入工作中实际应用。在调试时,某些代码需要暂时禁止执行,而测试其它代码,那么此时最好的方式就是注释代码。
2.添加注释的方法
对代码添加注释有三种方式:Rem和单引号(也称撇号),以及工具按钮法。
(1)利用Rem添加程序注释
Rem:用来在程序中添加注释。其语法如下:
Rem comment
VBA对于注释行自动以绿色呈现,以示区别。例如以下代码中,在过程上一行即为注释,阐述当前过程的功能。
Rem 程序功能:获取Excel用户名称
Sub UserName()
MsgBox
End Sub
在任何代码窗口录入以上代码,Rem注释行都呈绿色显示,见图所示。
图 为过程添加功能注释
如果需要对某行代码进行注释,有两种方式:注释在VBA代码上方及右方。
当需要注释置于代码上方时,录入注释后再在次行录入代码。见图所示:
图 为代码添加含义注释
也可以仅仅对重要的代码或者不容易明白含义的代码进行注释,以缩减程序的大小。
如果需要在过程右方添加注释,使代码看起来更紧凑,则需要借用冒号来完成。例如:
Sub UserName(): Rem 程序功能:获取Excel用户名称
MsgBox : Rem 利用UserName属性获取用户名称
End Sub
VBA仍然将注释部分绿色显示,和代码加以区别,见图所示:
图 在代码右方添加含义注释
注意“Rem和注释之间必须有空格,否则将产生编辑错误。
(2)利用半角单引号添加程序注释
在使用效率上,本方法较Rem法更简单。直接在注释前添加一个半角单引号即可。在引号与注释之间不需要空格。
和Rem法一致,也可以在代码的上方或者右方添加注释。例如获取今天日期及星期的过程:
Sub 今天()
MsgBox Format(Date, "AAAA")
MsgBox Format(Date, "yyyy年mm月dd日")
End Sub
添加注释后可以有两种效果,见图所示:
图 利用单引号对代码添加注释
在添加注释时,如果注释与代码呈上下行关系者,则注释尽量与代码对齐。如上图中第一个过程,过程名称未缩进,那么代码的注释也不需要缩进;过程中间的代码有缩进四个单位,那么注释也相应地缩进四个单位。
如果代码与注释呈前后关系,则在代码与注释之间应添加几个空格。
虽然以上规则并非必须执行,但遵循本规则会使代码美观,给阅读者带来便利。
(3)用工具栏按钮批量注释代码
在“编辑”工具栏中有一个专用于“设置注释块”和“解除注释块”的按钮,分别用于批量添加注释及批量解除注释。图中鼠标指向的图标即为设置注释专用工具,其右边第一个按钮则用于解除注释。
图 设置注释及解除注释的按钮
使用工具设置注释的步骤为:选择代码(可以是多行,甚至多个过程)(单击“设置注释块”按钮。
该工具按钮添加注释是以行为单位,无法从代码中间某个字符开始设置注释。所以选择代码时只需要定位于该行任意位置再单击按钮“设置注释行”即可。
例如以下过程,获取今天的日期提供了三种方式,现暂时仅需第三种,但不除排以后使用前两种格式的可能。那么删除前两行代码显示是不可取的,注释前两行使其暂是不运行,需要时再解除注释才是最佳选择。
Sub 今天()
MsgBox Format(Date, "DDDD yyyy年mm月dd日")
MsgBox Format(Date, "DDD yyyy年mm月dd日")
MsgBox Format(Date, "AAAA yyyy年mm月dd日")
End Sub
注释前两行时, 鼠标选择两行代码的任意位置再单击“设置注释块”均可,例如图。
图 批量设置注释
后续需要该代码时,不再重新编写代码,选择目标代码行的任意位置,然后单击“解除注释块”即可。
提示:批量设置注释块时,如果需要注释掉整个过程,那么需要选择过程的所有行再执行,不可单独留任意一行代码,否则可能产生编译错误。
3.设置注释在调试代码中的作用
调试代码是每个程序员的必然经历。
鉴于测试环境或者数据与实际工作中可能存在的差异,那么调试代码时部分代码不需要它执行,但在调试时又不能直接删除它。而在代码前加一个单引号使其不执行才是上策。
例如以下代码用于检测B2:B10000的成绩,如果成绩单元格为空白则提示用户录入成绩,如果成绩小于60分则对该单元格填充红色背景。
Sub 标示小于60分的成绩()
For i = 2 To 10000 '遍历B2:B10000
IF Len(Cells(i, 2)) = 0 Then MsgBox Cells(i, 1).Address(0, 0) & "没有录入成绩"
IF Cells(i, 2) <610 Then Cells(i, 2). = 3
Next
End Sub
在测试时,往往随意录入几个数据,而造成B2:B10000区域中存在大量的空白单元格,那么调试此代码时将弹出无数个对话框。为了避免此问题,可以将代码中选择第三句设置为注释,暂时不执行该语句,在正式投入工作中使用时再解除注释即可。
另外,对于调试具有破坏型的代码时也有必要将部分代码禁用,测试其它代码。例如批量清除数据、图片或者批量添加条件格式等等。
4.对插件添加声明
对于大中型插件或者其它成品软件,有必要对工具进行说明,包括功能、版本、更新项目等等。通常置于程序最顶端。
例如双身份证号码获取信的插件,可以在前面加入一些信息。见图所示:
图 对身份证函数插件添加功能与版本声明1
也可以采用以下方式对程序进行说明:
图对身份证函数插件添加功能与版本声明2
在对工具添加说明注释时,其实有必要进行一些美化,让终端用户感觉更赏心悦目。况且程序的美化与程序完善是没有任何冲突的。
在本书第29章将带领大家设计一个美化VBA代码的工具,提供一些可选的美化注释,用户仅仅需要单击即可完成上述美化注释。
长代码分行
VBA代码每个允许存放1到1024个字符。然而为了提升阅读和修改的便利性,应尽量不要超过200个字符,或者尽量让一行代码不超过当前屏幕的可见宽度,使用户查看代码时不需要移动滚动条。
对代码进行分行,其方法如下:
从整行代码截断点处插入空格,再录入下划线“_”,最后将单击回车键,使后面的内容置于第二行。
假设将整行代码包括“AAAABBBB”A,需要在第四个“A”之后截断两行,那么代码截前后的对比图见图所示。
图 代码换行
通常需要换行的代码都是带有较长文本、具于说明性质的消息框。例如以下用于获取磁盘信息的代码:
Sub 磁盘信息()
Dim 盘符 As String, 类型 As String
For i = 1 To 26
On Error Resume Next
盘符 = Mid("ABCDEFGHIJKLMNOPQRSTUVWXYZ", i, 1)
Select Case CreateObject("").GetDrive(盘符 & ":").DriveType
Case 0: 类型 = "无法识别"
Case 1: 类型 = "移动磁盘"
Case 2: 类型 = "固定磁盘"
Case 3: 类型 = "网络磁盘"
Case 4: 类型 = "光盘DVD"
Case 5: 类型 = "虚拟磁盘"
End Select
IF <> 68 Then
msg = msg & CreateObject("").GetDrive(盘符 & ":").DriveLetter & " " & 类型 & " " & CreateObject("").GetDrive(盘符 & ":").SerialNumber & " " & CreateObject("").GetDrive(盘符 & ":").TotalSize / 1024 & " " & CreateObject("").GetDrive(盘符 & ":").FreeSpace / 1024 & Chr(10)
End IF
Next i
MsgBox msg
End Sub
代码的含义在本书第19章将进行详述,此得仅仅需要理解长代码如何分行显示。
本例的代码中,Msgbox语句的代码过长,超过两屏的宽度才能显示完成,这非常不利于阅读。可以通过以下方式截成五行:
msg = msg & _
CreateObject("").GetDrive(盘符 & ":").DriveLetter & " " & 类型 & " " & _
CreateObject("").GetDrive(盘符 & ":").SerialNumber & " " & _
CreateObject("").GetDrive(盘符 & ":").TotalSize / 1024 & " " & _
CreateObject("").GetDrive(盘符 & ":").FreeSpace / 1024 & Chr(10)
成五行后的代码不仅看方便,对于理解代码的含义也有益处。其中第二行开始,每行获取一个磁盘的一类信息。第二行DriveLetter表示卷标,第三行SerialNumber表示序列号……
本例文件参见光盘:..\ 第九章\代码分行.xlsm
如果需要从一个字符串中间截成两行,那么需要对截断后的两段字符串补齐双引号,且添加连接符。例如:
Sub test()
MsgBox "123456789123456789"
End Sub
本过程中Msgbox 语句截断为两行后,效果如下:
Sub test()
MsgBox "123456789" _
& "123456789"
End Sub
如果双引号不配对,将产生编辑错误。
代码缩进对齐
代码缩进是指在代码的前面根据其层次不同,添加不同数量的空格,或者使用Tab键缩进若干个单位。
代码缩进也不是必须的,但是对于阅读代码却有较大的帮助。所以在程序开发中,应尽量对代码进行缩进,使其更具有层次感。例如图,使用缩进功能后对于理解代码含义存在积极作用:
图 代码缩进
代码缩进有两种方式:手动缩进一行和利用工具批量缩进
1.单行代码手动缩进
如果是单行代码需要缩进,或者有多行代码需要缩进,但是缩进的单位不一致,那么可以手动对各行逐一缩进。
缩进的方式为:将光标定位于代码前面,按下Tab键,默状态下一次缩进4个单位。用户可以通过“选对”对话框“编辑器格式”中的“Tab键宽度”来指定这个缩进单位。
2.利用工具栏按钮批量缩进
VBE中的“编辑菜单”有一个“缩进”和一个“突出”按钮,它们都可以对选择的代码行进行批量操作。如果需要多行代码缩进相同宽度,那么应该使用本“缩进”按钮来完成;如果需要将多行代码取消缩进,那么应该使用“突出”按钮来完成。
用以下代码为例,将其缩进为图之样式:
Sub 生成菜单()
With (1).(msoControlPopup, 1, , 3, 1)
.Caption = "我的菜单(&M)"
With .(msoControlButton, 1, , , True)
.Caption = "菜单一(&One)…"
.OnAction = "one"
.Style = msoButtonIconAndCaption
.FaceId = 225
End With
With .(msoControlButton, 1, , , True)
.Caption = "菜单二(&Two)"
.OnAction = "Two"
.Style = msoButtonIconAndCaption
.FaceId = 300
End With
End With
End Sub
缩进步骤为:
(1)选择第5到8行,并单击工具栏的“缩进”;
(2)选择第11到14行,并单击工具栏的“缩进”;
(3)选择第3到15行,并单击工具栏的“缩进”;
(4)选择第2到16行,并单击工具栏的“缩进”;
最后的效果即为图所示。
如果需要所有代码左对齐,从向实现代码的减肥,那么可以选择所有代码后,多次单击“突出”按钮,直到所有代码左对齐。
本例文件参见光盘:..\ 第九章\代码缩进.xlsm
声明有意义的变量名称
在本书第5章中已经讲述关于声明变量的好处,事实上变量的名称也需要进行规范。虽然“ABC”或者“One”、“数量1”等等都是合法变量名,然而对于用户理解代码含义却带来障碍。而正确的命名方式通常有三种:
以数据类型简写为准
例如声明一个String型变量可以使用以下方式:
Dim str as string
此方式的优点是不管在何处看到本变量就立刻明白它的数据类型是String,用于文本字符串。
而声明一个Integer型的变量则可以使用以下方式
Dim inte As Integer
而声明一个工作表对象变量则用以下方式:
Dim sht As Worksheet
以变量作用进行简要描述
本方式则利用变量的作途来做为变量名的参考。
例如需要遍历工作表中所有图形对象,那么对变量名声明为“图形计数”或者“Shape_count”等等,熟悉英文就是英文对变量进行描述,否则用中文。
Sub 图形左对齐()
Dim 图形计数 As Integer
For 图形计数 = 1 To
(图形计数).Left = 0
Next
End Sub
两者综合
为了让他人对代码中的变量更利于理解,也可以将两种方式结合来命名变量。
例如“图形左对齐”过程,可以使用以下方式声明变量:
Dim Shape_int As Integer
其中前一段Shape表示这是一个用于图形对象的变量,后一段Int则表示它的数据数型是Integer。而两段之间用下划线“_”连接便于区分。
本方式优势在于可以准确、迅速理解变量的含义,缺点是变量名长。读者可以根据自己的喜好在三种方式中选择。
IF…end if类配对语句的录入方式
VBA中有很多类似于IF …End if这类需要配对的语句。例如
With…End With、For each…Next、For…next、Do…Loop
VBA可以对“Sub”配对,全自动生成“End Sub”,但以上几种语句却需要开发者自己录入完整。
对于初学者,常常遇到图和图所示编辑错误,笔者也不例外。在经历过无数次烦恼后决定改变代码录入方式,从而彻底杜绝这种错误。
图 IF语句录入不完整 图 For语句录入不完整
例如IF语句,其正确的录入步骤是:
(1)录入“IF…Then”;
(2)单击两次回车键后录入“End IF”;
(3)按下上箭头返回第二行,接着录入其它代码。
简单的说就是将“IF”和“End if”中间的代码与“End if”交换录入顺序,从而确保不会因忘记录入“End if”而产生编译错误。
对于其它需要配对的代码同样如此。
录入事件代码的方式
对于VBA中所有事件过程的代码,尽量通过对象和过程下拉列表来录入Sub过程的程序外壳。包括工作表事件、工作簿事件、以及窗体事件、窗体中所有控件的事件。
部分过程有多个参数,在录入代码时很难把握准确,而从下拉列表选择则快速无误。图是窗体中选择方式录入“CommandButton1_BeforeDropOrPaste”事件的截图。
图 通过下拉框输入事件过程
借用自动列出程序录入代码
VBA中数千个属性与方法,任何人都无法准确地记得所有单词。而手工录入语句则产生误差的机率将非常大,而且速度较慢。借用“自动列出成员”功能有利于准确录入代码。
录入调用激法程序的方法“AppActivate”因单词较长,不利于记忆,也不便于输入,那么仅仅实际工作中仅仅知道它是“APP”开头即可,其它字符可以借用“自动列出成员”来完成。方法如下:
(1)录入“VBA.”,注意使用半角状态下的小圆点,此时将弹出一个下后列表供用户选择,见图所示。其中“VBA”是AppActivate的库,而ScreenUpdating的库则是“Application”;
(2)移动下箭头,当移到目标上之后按下快捷【Tab】即可录入完整代码。
图 VBA对象的所有方法与属性列表
如果需要录入在窗体中录窗体的各种属性或者控件名称,则可以使用“Me.”来产生下拉列表;而需要录入应程序程的各种属性时,则改用“Application.”,VBA会列出所有Application的成员列表用户选择。
如果所有属性、方法、子对象都采用此方式输入,那么在程序代码中将会产生很多“Me.”或者“VBA.”,而事实上删除这些代码仍然可以正常执行,而且速度更快。所以在程序代码编写完成后,可以批量替换掉所有不需要的对象库。
例如程序中有多个“Me.”,那么批量替换步骤为:
(1)按下快捷键【Ctrl+H】打开“替换”对话框;
(2)将“查找内容”设定为“Me.”,将“替换为”目标保持空白即可;
(3)勾选“全字匹配”,然后再单击“全部替换”按钮即可替换掉所有“Me.”,其方对象库的替换方式相同。替换对话框的设置见图所示
图 设置替换内容
善用公共变量
如果过程中的运算结果需要在过程2中调用,那么可以将过程1的运算结果赋与某个辅助单元格或者写入注册表中,在过程二中则调用这个辅助单元格的值或者读取注册表。
虽然以上方式完全可行,但是需要操作单元格对象或者注册表对象,在执行效率上不太理想。而最佳方式是借用公式变量做为过渡,让过程二直接调用变量即可。
Public Sums As Long
Sub 过程一()
Rem 汇总工作表
Sums = (Sheets(1).UsedRange)
End Sub
Sub 过程二()
Rem 调用公共变量
MsgBox Sums
End Sub
在以上代码中,执行过程一后,变量Sums即已赋值,只要工作表不关闭,那么任何模块中都可以直接调用Sums的值,相比过程一中将汇总值存入单元格,过程二再读取单元格的值要快得多。
将较大的过程分为多个再调用
VBA允许一个过程存入上千条代码,然而一个过程太庞大不利于阅读和维护,特别是一个代码超过一屏时。此时需要将过程按作用分为多个子过程,再到主过程中调用。
Sub 主程序()
MsgBox "1"
MsgBox "2"
MsgBox "3"
End Sub
假设以上过程需要分为多个子过程,那么可以按以下方式进行:
Sub 主程序()
Call 过程一
Call 过程二
Call 过程三
End Sub
Sub 过程一()
MsgBox "1"
End Sub
Sub 过程二()
MsgBox "2"
End Sub
Sub 过程三()
MsgBox "3"
End Sub
但是对于一个横跨两屏的With语句或者IF语句则不宜截断。例如“With”在第一屏中,而“End with”在第二屏,则不宜将With语句分为两个过程。
减少过程参数
VBA中的Sub过程和Function过程都支持200个以上的参数,但为了便于维护和理解,自定义函数尽量不要超过5个必选参数。
兼容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 2007
[A1048576]——不适用于Excel 2003
甚至利用版本号来判断也是无法达到通用的,例如:
Range("A" & IIF( = 12, 1048576, 65536))
以上代码引用A列最后一个单元格,虽然利用Version来判断版本号,对Excel 2007使用1048576,对Excel 2003使用65536,但仍然忽略了一个重点:Excel可以使用兼容模式。
而最精典的引用是:
Cells(, 1)——A列最后一个单元格
Cells(1, )——第一行最后一个单元格
Cells(1, ).EntireColumn——最后一列
Range( & ":" & )——最后一行
3.菜单与功能区模式
Excel 2007采用新的功能区,与以往任何版本的Office软件菜单在外观上或者代码编写上都完全不同。
Excel 2003的CommandBars对象虽然被Excel 2007保留了下来,但其Position属性不再发生任何作用,即不管如何设置,自定义工具条都显示在功能区中,而不会像Excel 2003一样可以在屏幕上、下、左、右任意切换。
为了让程序的兼容性更好,尽量使用菜单,而非功能区或者工具条。
优化代码
VBA爱好者都追求所有代码可以正确执行,准确地获取需求的结果。然而要做一个专业的程序员则不能止于准确,而需要追求高效。本节介绍一些优化代码、提升速度的方法。
强制声明变量
VBA并不要求用户必须声明每个变量,VBA会自动为每个变量分配数据类型。
这是相对于其它程序软件的一个优点,即兼容性好。然而同时也是一个缺点,不声明变量其类型时程序在执行时将会消耗更多的内存。相当于牺牲效率换取兼容性。
为了提升程序的效率,在编写代码时,应尽量将所有变量显示声明,除非某个变量的类型无法把握(初学者较常见)。
善用常量
如果某个数值或者字符串在程序中反复出现,那么尽量声明一个常量做取代该值,将后在代码中直接调用常量。
关闭屏幕更新
在单元格中写入数据或者批量插入图形对象时,每执行一句代码屏幕会更新一次,而更新屏幕需要时间。在大多数情况下,完全没有必要更新屏幕的状态。开发者可以关闭屏幕更新来提升效率,等所有过程执行完毕后再恢复屏幕更新即可。
VBA提供了一个可以控制屏幕更新开、关的属性:ScreenUpdating。它的语法如下:
=True/False
如果将该属性值设为True则允许屏幕更新,否则禁止屏幕更新。
下面举例证明。以下过程为隐藏Excel 2007所有偶数列:
Sub 隐藏偶数列()
Dim Col As Integer, Tim As Long
Tim = Timer
For Col = 1 To
IF Col Mod 2 = 0 Then Cells(1, Col). = True
Next
MsgBox "程序共运行了" & Format(Timer - Tim, "") & "秒"
End Sub
在笔者的电脑上,该过程的执行时间是46秒,如果用Excel 2003或者Excel 2007在兼容模式下使用,那么因为列数仅仅256列,时间会大大缩短。
如果在程序中关闭屏幕更新,那么代码的效率将大大提高。
Sub 隐藏偶数列()
Dim Col As Integer, Tim As Long
= False
Tim = Timer
For Col = 1 To
IF Col Mod 2 = 0 Then Cells(1, Col). = True
Next
= True
MsgBox "程序共运行了" & Format(Timer - Tim, "") & "秒"
End Sub
在笔者的电脑上,该过程的执行时间是秒,与前一个未关闭屏幕更新的过程相比,在效率上提高了40多倍。
“ = False”这一句代码通常需要放置于循环语句之前,在循环完成后再恢复屏幕更新,否则会影响正常工作。
本例文件参见光盘:..\ 第九章\隐藏偶数列.xlsm
利用WITH减少对象读取次数
VBA中读取对象需要花费一定的时间,而且对于多级对象(同时列出父对象与子对象)时需要的时间更长。例如以下两句代码:
[a1]
(1).[a1]
虽然它们都指向同一个单元格对象,对A1读写后的结果也完全一致,然而前者圆点更少,读取时间也相应更少。在VBA中引用对象时,每出现一个圆点就需要去读取一个对象,通过以下两段代码可以比较出“[a1]“和“(1).[a1]”在效率上的差异:
Sub 循环10000次1()
Dim tim As Long
tim = Timer
For i = 1 To 10000
[a1] = i
Next i
MsgBox "程序共运行了" & Format(Timer - tim, "") & "秒"
End Sub
以上代码在笔者的电脑上执行时间是秒。
Sub 循环10000次2()
Dim tim As Long
tim = Timer
For i = 1 To 10000
(1).[a1] = i
Next i
MsgBox "程序共运行了" & Format(Timer - tim, "") & "秒"
End Sub
以上代码在笔者的电脑上执行时间是秒。虽然在不同的电脑上执行时间会有所区别,但是第二个过程的执行时间长于前一个过程的时间却是一定的。
With语句正是解决这类问题的,本书第7章中已讲述利用With来简化对象的引用次数,而引用次数减少的同时,也提高了代码的执行效率。
利用With来简化对象读取及提高执行效率,可以从以下案例体现:
Sub 设置字体()
Range("A1"). = "黑体"
Range("A1"). = "加粗 倾斜"
Range("A1"). = 11
Range("A1"). = xlUnderlineStyleNone
Range("A1"). = 192
End Sub
以上代码中需要引用单元格对象和字体对象(Font)五次。
Sub 设置字体()
With Range("A1").Font
.Name = "黑体"
.FontStyle = "加粗 倾斜"
.Size = 11
.Underline = xlUnderlineStyleNone
.Color = 192
End With
End Sub
以上代码需要引用单元格对象和字体对象(Font)一次。
如果单独执行以上两段代码,在执行效率上可能是无法感觉到,然而在一个大中型程序中,多段代码综合后,每句代码的小小差异累积起来就会对程序较大的影响了。所以在编代码时有必要对每个细节进行优化。
利用变量减少对象读取次数
如果某个变量在一个过程中多次出现,应考虑用一个变量来替换该对象。因为变量存在内存中,VBA读取内存数据远远快于对象。
例如以下代码:
Sub 对小于B1的单元格填充背景1()
Dim tim As Long, rng As Range
tim = Timer
For Each rng In Range("A1:A20000")
IF rng > [b1] Then = 3
Next
[B2] = Format(Timer - tim, "") & "秒"
End Sub
在代码中,单元格B1被引用了20000次,程序的执行时间在笔者的电脑上大概秒钟。
Sub 对小于B1的单元格填充背景2()
Dim tim As Long, rng As Range, 标准 As Byte
tim = Timer
标准 = [b1]
For Each rng In Range("A1:A20000")
IF rng > 标准 Then = 3
Next
[B3] = Format(Timer - tim, "") & "秒"
End Sub
在修改后的代码中,单元格B1仅仅需要读取一次。在后面的循环中,单元格A1不再参与运算,而是内存中的变量“标准”在参与运算。该代码的执行时间少于1秒钟。
本例文件参见光盘:..\ 第九章\对小于B1的单元格填充背景.xlsm
善用带$的字符串处理函数
在VBA中,有两套字符串处理函数,包括带“$”和不带“$”的函数,例如Mid和Mid$,Left和Left$,Right和Right$。
如果使用不带“$”符号的函数计算字符串,那么VBA将字符串作为变体型数据进行计算,而使用带“$”的函数时则将字符串当做String类型进行计算。显示变体型数据在计算时需要更多的内存空间。
例如以下两句代码,第二句在执行效率上会稍占优势:
Reault = Mid("中华人民共和国", 3)
Reault = Mid$("中华人民共和国", 3)
善用循环中的步长减少循环次数
当使用有针对性的For循环,即仅仅需要对循环对象中的部分对象进行操作时,应该调整循环的步长来减少循环的次数。
例如将奇数行添加背景色,步长为1的For循环代码如下:
Sub 前10000行背景着色()
tim = Timer
For i = 1 To 100000
IF i Mod 2 = 1 Then Cells(i, 1). = 15
Next i
MsgBox Format(Timer - tim, "") & 秒
End Sub
本代码需要循环的次数是10000次
Sub 前10000行背景着色2()
tim = Timer
For i = 1 To 100000 Step 2
Cells(i, 1). = 15
Next i
MsgBox Format(Timer - tim, "") & 秒
End Sub
本代码需要循环的次数是5000次,其执行结果与前一个过程完全一致。读者可以分别执行两个过程测试其时间。
利用数组代替单元格对象
VBA处理数组的速度远远大于处理对象的速度,对于可以利用数组来替换对象的都尽量使用数据,例如1000个图片的名称,或者1000个单元格,或者不确定个数的工作表名称。
关于数组的概念和具体应用,在本书第十三章和第十四章将进行详述,此处仅仅需在了解数组的处理速度快于对象即可。
下面举两个实例,证实数组的字符处理方面的优势。
实例一:对3000个学生中不及格成绩标示“不及格”
实现此功能可以两种方式,包括使用数组及非数组。
Sub 对小于60分成绩进行注释()
Dim i As Integer, tim As Long '声明变量
tim = Timer '获取当前时间
'从2开始至最后一个非空单元格结束
For i = 2 To Cells(, 1).End(xlUp).Row
'如果小于60则在右边单元格标注“不及格”
IF Cells(i, 2) < 60 Then Cells(i, 3) = "不及格"
Next i
'报告时间
MsgBox Format(Timer - tim, "") & 秒
End Sub
该过程会对3000个学生的成绩(假设工作表中是3000个成绩)逐一进行判断,再将小于60的成绩对应的单元格写入“不及格”,它需要读取单元格3000次,写入单元格的次数随不及格人数而定。
Sub 对小于60分成绩进行注释2()
Dim i As Integer, tim As Long, arr1(), arr2() '声明变量,包括两个数组变量
tim = Timer '获取当前时间
'将成绩赋与数组变量,后续读取时不再读单元格,而是从内存中取值
arr1 = Range([B2], Cells(, 2).End(xlUp))
'重置第二个数组变量大小
ReDim arr2(1 To UBound(arr1), 1 To 1)
'循环数组
For i = 1 To UBound(arr1)
'如果数组中某元素小于60则对第二个数组对应的值赋值为“不及格”
IF arr1(i, 1) < 60 Then arr2(i, 1) = "不及格"
Next i
'将第二个数组的值赋与单元格
Range([C2], Cells(, 2).End(xlUp).Offset(0, 1)) = arr2
'报告执行时间
MsgBox Format(Timer - tim, "") & 秒
End Sub
该过程首先将3000个学生成绩赋与数组,然后所有计算都基于数组中的元素,不再读取单元格,最后再将结果一次性写入区域。整个过程读取单元格一次,写入单元格也只有一次。它在执行效率上大大高于前一个过程。
本例文件参见光盘:..\ 第九章\利用数组标示不及格成绩.xlsm
实例二:获取1000个工作表名称
当前工作簿中在1000个工作表名,将1000个工作表的名称在A列罗列出来。仍然采用数组与非数组两种方式完成:
Sub 工作表目录() '非数组法
Dim i As Integer, Tim As Long
Tim = Timer '获取当前时间
For i = 1 To '遍历所有工作表
Cells(i, 1) = Sheets(i).Name '从A1开始逐一建立工作表目录
Next i
'报告时间
MsgBox Format(Timer - Tim, "") & 秒
End Sub
该过程未采用数组,需要读取1000次工作表对象的名称,也要对单元格对象写入1000次。在笔者的计算机上执行时间超过4秒钟。
Sub 工作表目录2() '数组法
'声明变量
Dim i As Integer, Tim As Long, arr(), Rows_count
Tim = Timer '获取当前时间
Rows_count = '获取工作表数量
ReDim arr(1 To Rows_count, 1) '重置数大小
For i = 1 To Rows_count '遍历工作表
arr(i, 1) = Sheets(i).Name '将每个工作表名存入数组中
Next i
[a1].Resize(Rows_count, 1) = arr '将数组赋与单元中形成目录
'报告时间
MsgBox Format(Timer - Tim, "") & 秒
End Sub
该过程采用数组完成,读取工作表对象的次数是1000次,但写入单元格对象的次数只有一次。在笔者的计算机中执行时间在秒钟左右,较前者的效率提高多倍。
本例文件参见光盘:..\ 第九章\建立工作表目录.xlsm
不重复调用自定义函数时不使用自定义函数
在VBA中,对于一些较短小的自定义函数,调用函数往往比函数过程的执行时间都长。对于简单的运算尽量不使用自定义函数,而是直接将函数的过程写在Sub过程中。
另外对于某个自定义函数在Sub过程中如果仅仅出现一次,那么也尽量不定义Function过程让Sub调用,而是将Function过程中所有代码直接写入Sub过程。因为调用Function过程本身需要花费额外的时间,只有要Sub过程中需要多次调用该Function过程时才需要定义一个Function过程。
将不改变值或者属性的语句放到循环语句外
循环语句都需要反复运行,如果错误将不需要循环执行的语法置于循环体中将会消耗额外的内存,占用不必要的时间。
例如以下过程:
Sub 工作表目录3() '非数组法
Dim i As Integer, Tim As Long
Tim = Timer '获取当前时间
For i = 1 To '遍历所有工作表
[a1] = "工作表目录"
Cells(i + 1, 1) = Sheets(i).Name '从A1开始逐一建立工作表目录
Next i
'报告时间
MsgBox Format(Timer - Tim, "") & 秒
End Sub
该过程是在A1输入“工作表目录”,然后在A1单元格之后建立工作表目录。因“[a1] = "工作表目录"”语句放置在循环体中,那么工作表有1000个,这就会执行1000次,而事实上仅仅需要执行一次。本过程在笔者的计算机中执行时间近10秒。
Sub 工作表目录4() '非数组法
Dim i As Integer, Tim As Long
Tim = Timer '获取当前时间
[a1] = "工作表目录"
For i = 1 To '遍历所有工作表
Cells(i + 1, 1) = Sheets(i).Name '从A1开始逐一建立工作表目录
Next i
'报告时间
MsgBox Format(Timer - Tim, "") & 秒
End Sub
该过程可以完成同样的功能,但将“[a1] = "工作表目录"”置于循环体外,程序的执行时就可以减少一半,大概5秒钟可以完成。
利用长度计算判断单元格是否非空
在VBA中,读取单元格字符关判断其是否空白,相对于读者单元格字符长度需要的时间更长,所以如果需要判断某单元格是否空白不要用以下语句:
Range("a1") = ""
应该改用:
Len(Range("a1")) = 0
尽量调用内置功能
有很多操作,可以编写VBA代码计算来实现,也可以调用Excel的内置的功能实现。开发者应尽量借用内置功能实现,除了速度快之外,调用内置功能的代码也简单许多。
例如对一列数据排,虽然可以对数据逐个进行大小比较,然后再按大小顺序排列来完成需求,但在写法上以及效率上较一句代码调用内置排序功能Sort会差一些。
当然,在某些情况下也有例外,例如对一个数组“{1, 7, 8, 6, 9, 3, 5, 7, 6, 8, 9, 4, 1, 2, 4}”计算最大值,调用工作表函数Max仅仅一句代码,而VBA利用循环来获取最大值则代码会长许多倍,但经过测试比较,它的效率却提高许多。
两段代码如下:
Sub 获取数组最大值() '循环执行100000次 VBA法
Dim arr(), temp As Byte, i, j, tim As Long '声明变量
tim = 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 = arr(1) * 1 '将数组中第一个值赋与变量Temp
For i = 1 To UBound(arr) '循环比较数组中所有元素
'如果数组中某元素大于变量Temp,则将该值赋与变量Temp
IF arr(i) > temp Then temp = arr(i)
Next i
Next j
'最后报告最大值及执行时间
MsgBox "最大值为" & temp & Chr(10) & "执行时间:" & Format(Timer - tim, "") & "秒"
End Sub
该过程采用纯VBA法,利用循环获取最大值,执行100000次后其时间大概在1秒钟左右。
Sub 获取数组最大值2() '循环执行100000次 工作表函数法
Dim arr(), temp As Byte, i, j, tim As Long
tim = 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 = (arr) '利用工作表函数一次性获取最大值
Next j
'最后报告最大值及执行时间
MsgBox "最大值为" & temp & Chr(10) & "执行时间:" & Format(Timer - tim, "") & "秒"
End Sub
该过程采用工作表函数进行计算,代码精简一些,不使用循环即计算出数组的最大值。然后它的执行时间大概在8秒钟左右。与前者在效率一存在较大的差异。
本例文件参见光盘:..\ 第九章\获取数组最大值.xlsm
利用对象循环替代单元格循环
如果对当前表中与单元格关联的对象进入某项操作时,如果循环单元格方式可以完成,循环其它对象也可以完成,那么尽量对其它对象进行循环,从而减少循环次数。
例如当前表中有多个图片,统计有多少单元格被图片覆盖,那么利用单元格循环,逐个判断它是否被覆盖的方式可以达成需求。而利用图片循环,逐个统计图片的覆盖区域的方式也可以完成。然而工作表中单元格数量通常远远多于图片数量,那么单元格循环的次数自然也超过图片循环的次数。
再如统计当前表所有带有批注的单元格,如果逐个单元格判断是否具有批注,最后再合计是一种可行的方法。但更好的是循环批注,直接找到每个批注,再利用Parent获取该批注的父对象单元格即可。假设工作表中已区域有10000个单元格,其中只有两个单元格具有批注,那么循环单元格的方式需要判断10000次,而循环批注的方式仅仅需要循环两次罢了。
两种循环方式的代码如下:
Sub 单元格循环() '获取具有批注的单元格地址
Dim rng As Range, Address As String, bl As Boolean
On Error Resume Next
For Each rng In
bl =
IF Err = 0 Then Address = Address & (0, 0) & Chr(10)
:
Next rng
MsgBox Address
End Sub
Sub 批注循环() '获取具有批注的单元格地址
Dim com As Comment, Address As String
For Each com In
Address = Address & (0, 0) & Chr(10)
Next
MsgBox Address
End Sub
编程需要涉及很多对象,不同工作人员所涉及的对象各自不同。根据经验,不同行业的程序员会有不同的提速方法。
本章仅对提速方面略作介绍,限于篇幅及个人经验所限,无法为读者罗列出所有代码优化技巧。随着编程时间的增长,读者也会将有自己的心得,编写出效率更高的程序。
第十章
常用语法剖析
学习VBA,最重要的是语法,其它的对象、属性等等不需要花费太多时间去记忆,可以借助VBA内部的“自动成员列表”来完成输入。
VBA中涉及的语法很多,其中常见有输入输出语句、循环语句、条件语句、With语句及防错语句。如何驾驭好这些语句对于程序员来说一个非常重要的课题。
本章要点:
输入、输出语句
条件判断语句
循环语句
With语句
错误处理语句
输入、输出语句
以前面章节中,出现了大量的输入、输出语句,本节对VBA中的输入、输出语句进行详细地讲解。
Msgbox函数的功能及作用
VBA最常见的信息输出函数是Msgbox函数,在任何VBA的书籍中,它所出现的频率都是最高的。
在英文中,Msg表示Message,即消息,而Msgbox则表示信息框。顾名思义,Msgbox是用于在屏幕中显示一些信息的对话框,告诉用户需要做什么,或者程序运算的结果,或者某操作的步骤说明等等等等,它的作用极其广泛。
如果一定要对Msgbox的用途做一归纳,那么笔者认为可以站在开发者立场,对程序开发中它所具备的功能做一分类。
1.返回运算结果
告诉用户VBA的运算结果通常有三种模式:存入工作表、打印到文件和利用消息框返回结果。通常对于临时性的、不需要储存的信息可以利用对话框来展示,它的特点是关闭窗口后就完全消失,不占用任何内存空间。
2.询问执行方式
对于某些有多种执行选项的操作,例如隔行着色工作表,它可以对奇数行着色,也可以对偶数行着色,为了体现程序的通用性和灵活性时,往往弹出一个提示框让用户选择执行方式,这是最佳的程序开发思路。
例如图,用户单击不同按钮时,VBA会进行不同的执行方式。
图 利用Msgbox询间执行方式
3.提示执行步骤
在设计VBA程序时,如果后续需执行的操作较复杂,应该通过一个消息框来提示用户。包括该程序大概有多少步骤,各步骤中需要注意哪些问题,或者在什么情况下需要跳过什么步骤等,从而减少程序出错的机率。
4.告知错误原因
终端用户在执行VBA程序时,总会有或多或少的错误产生。有时是程序员粗心写错代码造成,有时是代码的兼容性造成,有时是代码完全正确但用户的数据不规范造成。而VBA很多时候返回的错误提示让人摸不着头脑,程序员有必要预先设置更有意义的错误提示,告诉用户产生此错误的可能情况。
例如,当工作表保护时执行以下语句,一定会产生图所示的错误提示:
[a1] = 1
从图片和代码进行分析,错误提示与代码实际出错的原因风马牛不相及,这完全不利于终端用户了解程序出错的原因。为了避免这种差错,开发程者需要通过一个信息框来展示更有意义的错误提示。例如图,用户可以立刻明白错误原因,从而修正执行方式。
图 工作表保护时执行单元格写入所产生的提示 图 定义更有意义的提示框
5.展示当前状态
类似于DOS程序中“请按任意键继续……”一样,VBA也可以在程序执行时显示当前的状态,然后当用户单击回车后再继续执行。
6.设计程序帮助
Excel自身的所有功能都有相应的说明信息,利用VBA开发程序时也需要设计一帮助系统,不管这个系统大小如何,一定要有相应的说明。通常包括三方面的内容:
(1)程序功能说明
(2)程序版本及各版本修改内容阐述
(3)本程序适用范围,即兼容哪些Office版本,或者适用哪个行业
当然,利用Msgbox制作帮助界面受字符长度限制,只能提供一些简单且简短的信息。
Msgbox函数的语法
Msgbox用于在对话框中显示消息,等待用户单击按钮,并返回一个Integer类型的值。当用户单击其中按钮后将返回信息给VBA,程序员可以根据这个返回值来决定后续的操作。
的参数
Msgbox的具体语法如下:
MsgBox(prompt[, buttons] [, title] [, helpfile, context])
其中Prompt参数是必选参数,其作参数是可选数。各参数功能详解如下表10-1所示:
表10-1 Msgbox参数详解
部分
描述
Prompt
字符串表达式,作为显示在对话框中的消息。prompt 的最大长度大约为 1024 个字符,由所用字符的宽度决定
Buttons
指定显示按钮的数目及形式、使用的图标样式、缺省按钮是什么以及消息框的强制回应等
Title
在对话框标题栏中显示的字符串表达式。如果省略 title,则将应用程序名放在标题栏中
Helpfile
字符串表达式,识别用来向对话框提供上下文相关帮助的帮助文件
Context
数值表达式,由帮助文件的作者指定给适当的帮助主题的帮助上下文编号
其中最重要的是前两个参数,第一个参数决定显示的信息,不超1024个字符的文本;第二个参数决定图标及按钮。
的按钮与图标
Msgbox的第二参数可以有多种组合,实现不同的按钮与图标样式。在表10-2罗列出了VBA中Msgbox提示的常数列表。
表10-2 Msgbox图标与按钮常数详解
常数
值
描述
vbOKOnly
0
只显示 OK 按钮
VbOKCancel
1
显示 OK 及 Cancel 按钮
VbAbortRetryIgnore
2
显示 Abort、Retry 及 Ignore 按钮
VbYesNoCancel
3
显示 Yes、No 及 Cancel 按钮
VbYesNo
4
显示 Yes 及 No 按钮
VbRetryCancel
5
显示 Retry 及 Cancel 按钮
VbCritical
16
显示 Critical Message 图标
VbQuestion
32
显示 Warning Query 图标
VbExclamation
48
显示 Warning Message 图标
VbInformation
64
显示 Information Message 图标
vbDefaultButton1
0
第一个按钮是缺省值
vbDefaultButton2
256
第二个按钮是缺省值
vbDefaultButton3
512
第三个按钮是缺省值
vbDefaultButton4
768
第四个按钮是缺省值
vbApplicationModal
0
应用程序强制返回;应用程序一直被挂起,直到用户对消息框作出响应才继续工作
vbSystemModal
4096
系统强制返回;全部应用程序都被挂起,直到用户对消息框作出响应才继续工作
vbMsgBoxHelpButton
16384
将Help按钮添加到消息框
VbMsgBoxSetForeground
65536
指定消息框窗口作为前景窗口
vbMsgBoxRight
524288
文本为右对齐
vbMsgBoxRtlReading
1048576
指定文本应为在希伯来和阿拉伯语系统中的从右到左显示
以上表10-2中,实际包括四组信息,其中第一组值 (0–5) 描述了对话框中显示的按钮的类型与数目;第二组值(16, 32, 48, 64) 描述了图标的样式;第三组值 (0, 256, 512) 说明哪一个按钮是默认值;而第四组值 (0, 4096) 则决定消息框的强制返回性。Msgbox的第二参数可以从四组中选择值相加来任意设置信息框的显示样式。
例如需要显示一个两行信息且带一个OK按钮的对话框,那么可以使用以下代码:
MsgBox "第一行" & Chr(10) & "第二行", vbOKOnly
执行后效果如图所示。
如果需要显示一个确定按钮与一个取消按钮,且标题显示为“提示”,那么可用以下代码:
MsgBox "现在开始执行?", vbOKCancel, "提示"
执行后效果见图所示:
图 两行信息且带一个OK按钮 图 一个确定按钮与一个取消按钮
如果同样是显示图的效果,如果需要默认选中第二个,那么代码可以修改如下:
MsgBox "现在开始执行?", vbOKCancel + vbDefaultButton2, "提示"
Msgbox的第二参数使用了“vbOKCancel + vbDefaultButton2”,即表示默认值为第二个按钮,当用户直接单击回车键时是按下“取消”而非“确定”。效果图所示:
如果在需要显示“是”和“否”按钮,再加入一个问号图标,且使用数字来表示按钮样式,那么代码如下:
MsgBox "继续执行下步?", 292, "继续"
代码中292的计算方式是:4 + 32 + 256,其中4表示显示“是”和“否”的按钮,32代表显示问号图标,而256则表示默认默认按钮为第二个。执行后效果见图所示:
图 一个确定按钮与一个取消按钮默认第二个 图 两个按钮加问号图标
当然,根据表10-2所示的常数值还可以有很多种组合,读者可以自行测试。
的返回值
Msgbox主要作用是显示一些信息给终端用户,然而对于程序开发者来说,更重要的一个功能是它具有返回值,且可以根据返回值决定下一步操作。
Msgbox的返回值有7种,见表10-3所示:
表10-3 Msgbox的返回值
常数
值
描述
vbOK
1
OK
vbCancel
2
Cancel
vbAbort
3
Abort
vbRetry
4
Retry
vbIgnore
5
Ignore
vbYes
6
Yes
vbNo
7
No
Msgbox的返回值对于开发者来说比较有用。例如用户单击“是”按钮时,执行后续的操作,如果单击“否”则直接退出程序。那么可以参考以下代码:
Sub 工作表改名()
'声明变量,用代获取Msgbox的返回值
Dim msg As VbMsgBoxResult
'获取Msgbox返回值
msg = MsgBox("将前表改名为今日期?", 292, "修改日期")
'如果用户单击是
IF msg = vbYes Then
'执行改名
= Date
Else
'否则退出程
Exit Sub
End IF
'其它更多代码.............
End Sub
执行以上代码时,将弹出图所示对信息框,如果用户直接单击回键,那么程序立即退出,不做任何回应;如果用户单击“是”按钮,那么立即以当前日期命名工作表,见图所示。
图 询问执行方式 图 选择“是”按钮则工作表改名
再举一个实例,仍然对工作表以当前日期重命名,但如果遇到有重名工作表时弹出包括“重试”、“忽略”和“终止”的对话框。代码如下:
Sub 工作表改名()
'声明变量,用代获取Msgbox的返回值
Dim msg As VbMsgBoxResult
'设置一个标签
err:
On Error Resume Next '防错,当出现错误时执行下一步
= Date '将当前工作表命名
IF > 0 Then '如果存在错误(即已经有工作表的姓称等于当前日期)
'获取Msgbox的返回值
msg = MsgBox("存在同名工作表,是否继续?", 2, "修改日期")
'如果用户单击“中断”则退出程序
IF msg = vbAbort Then Exit Sub
'如果用户单击“忽略”,则将当前表命名为日期,并添加左右括号
IF msg = vbIgnore Then = "(" & Date & ")"
'如果用户单击“重试”则清除错误设置,然后返回Err标签处继续执行
IF msg = vbRetry Then : GoTo err
End IF
End Sub
在以上代码中,Msgbox的第二参数使用2,即表示产生 “终止”、 “重试”、“忽略”和三个按钮,而单击三个按钮时分别对应Abort、Retry 及 Ignore三个返回值。
在过程中,如果命名时已经产生重命错误,那么Msgbox对话框才可能出现。如果用户单击对话框第一个按钮,则直接终止程序,利用“Exit Sub”来处理;如果用户单击第三个按钮,那么会忽略错误,将工作表命名为日期加括号; 如果用户单击第二个按钮,那么程序会继续返回首行继续执行程序,它将会循环产生同样的对话框,直到用户单击其它选项。
本过程的错误提示框见图所示,如果单击忽略,则程序执行结果见图所示。
图 重名时的提示信息 图 单击忽略时效果
本例文件参见光盘:..\ 第十章\ Msgbox处理重命错误.xlsm
在使用Msgbox的返回值时,Msgbox的各参数必须使用括号,而不需要返回值时,这不是一个表达式,则不需要括号。以下两种方式都是错误的Msgbox用法:
MsgBox( "你好! ", 64, "提示")
Result = MsgBox "你好! ", 64, "提示"
Msgbox函数的限制
Msgbox函数用于将信息展示给用户,它的应用极其广泛。然而它有自身的一些限制,做为程序员有必要了解它的所有限制,才能恰当地运用好Msgbox。
总体来说,Msgbox具有三方面的限制。
1.字符数问题
Msgbox有最大高度与宽度限制,而且只有在字符超过它的限制时才可以它体现出来。因为Msgbox具有自动缩放功能,当信息少时它会自动缩小信息框以适应字符的宽度,此时无法目视它的可用范围。要测试Msgbox可显示的最多字符数可以使用以下方式:
(1)在[A1]单元格中存放超1000个英文字母,在[a2]单元格中存放超过1000个汉字;
(2)在模块中录入以下语句:
Sub Msgbox测试()
MsgBox [a1]
MsgBox [a2]
End Sub
执行以上代码后,可以发现,英文字符可以显示1024个左右,而纯汉字只能显示511个。如果英文与汉字共用,那么按一个汉字占用两个英文宽度计算。
如果需要显示超过以上限制的字符信息,那么只能分屏显示,当然这不是好办法。通常采用窗体控件或者WScript技术来突破。
2.控制权问题
Msgbox对话框总是拥有焦点,且拥有绝对的控制权。即只要Msgbox对话框不关闭,那么代码都会停止运行,只有关闭对话框后才会交还控制权给程序,继续执行其它语句。
明白这一点很重要,如果程序在执行过程中需要显示信息,例如当前执行进行,而且不能影响程序地继续执行,那么Msgbox方式并非首选。建议使用状态栏信息或者无模体的窗体。
图即为利用状态显示进度来替换Msgbox的效果。
图 状态栏是示程序执行进度
3.时间性问题
Msgbox的对话框显示的对话框,如果用户不手动关闭,它会永远存在。如果用户需要它在指定时间自动关闭,那么Msgbox也无法达成需求。
通常采用三种方法来实现:用户窗体、WScript技术和API,在后面小节中将提进行演示。
利用WScript突破Msgbox限制
WScrip是一种脚本语言,它也可以实现输出对话框信息,而且可以突破Msgbox的一些限制。
利用脚本语言显示信息可用语言中的Popup方法。它的具体语法为:
(strText, [natSecondsToWait], [strTitle], [natType]) = intButton
其中第一参数是显示的信息,第二参数是显示的时间,表示信息框在多少秒钟的自动关闭,第三参数是标题,第四参数是按钮与图标的状态。本书第19章将进行更详细的解说。
语言中的Popup方法相对于Msgbox有两个优点:
可显示的字符远远超过1024
可以自动关闭对话框
如果A1单元格的超过2000个字符,那么可以使用以下语句显示A1字符串的文本框:
Sub 显示A1信息()
CreateObject("").Popup [a1], , "提示", 64
End Sub
如果需要信息显示3秒钟自动关闭,可以用以下代码:
Sub 自动关闭()
CreateObject("").Popup "三秒钟关闭", 3, "提示", 64
End Sub
但经过多次测试,以上语句在Excel 2003中工作良好,在Excel 2007中却无法自动关闭。所以为了体现通用性和稳定性,可以改用API来完成这个难题,让Excel 2003和Excel 2007都可以顺利实现3秒钟关闭。
API实现的方式如下:
Public Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElaspe As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
Dim TID As Long
Const Sec = 3 '可以在这里修改时间
Sub CloseTest(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
"~", True '发送回符,即关闭窗口的命令
KillTimer 0, TID
End Sub
Sub 三秒钟自动关闭()
TID = SetTimer(0, 0, Sec * 1000, AddressOf CloseTest)
MsgBox Sec & " 秒种自动关闭窗口", 65, "提示"
End Sub
本例文件参见光盘:..\ 第十章\ 3秒钟自动关闭的信息框.xlsm
也可以实现信息输出,不过通常开发人员使用。它只能在VBE中的立即窗口显示信息,而终端用户是不需要进入VBE界面的。
Print具体语法为:
[outputlist]
当Object为Debug时,它可以实现将信息输出到立即窗口,通常是开发者调试代码时使用。例如打开当前工作簿路径下的“VBA教学.xls”文件,在打开文件前程序员需要查看一下路径是否存在或者查看路径所在磁盘名,那么可以通过“”方式将信息显示在窗口。代码如下:
Sub 打开当前工作簿同路径下的文件()
Dim Paths As String '声明变量
Paths = '获取路径
Paths '查看路径
Filename:=Paths & "VBA教学.xls" '打开工作簿
End Sub
执行以上代码后,在立即窗口中将产生路径,见图所示。
图 在立窗口显示信息
提示:如果工作簿未保存,那么将不存在路径,输入的信息只是一个空文本。
Inputbox函数的功能与作用
Inputbox函数是VBA中用于数据输入的函数,它可以在一对话框来中显示提示,等待用户输入信息或按下按钮,返回用户输入的String类型字符串。
Inputbox通常用于为用户提供录入窗口,然后将返窗口中的录入字符串按代码指定方式导入到相应的窗口或者根据输入值来决定后续的操作。
例如图中,用户的录入信息决定程序的后续执行方式。
图 以录入值确定计算方式
Inputbox函数的语法
Inputbox的具体语法如下:
InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])
其中第一参数是必选参数,其余参数为可选参数。各参数的详细解释如下:
表10-4 Inputbox的参数详解
部分
描述
Prompt
作为对话框消息出现的字符串表达式。prompt 的最大长度大约是 1024 个字符,由所用字符的宽度决定。
Title
显示对话框标题栏中的字符串表达式。如果省略 title,则把应用程序名放入标题栏中
Default
显示文本框中的字符串表达式,在没有其它输入时作为缺省值
Xpos
数值表达式,成对出现,指定对话框的左边与屏幕左边的水平距离。如果省略 xpos,则对话框会在水平方向居中
Ypos
数值表达式,成对出现,指定对话框的上边与屏幕上边的距离。如果省略 ypos,则对话框被放置在屏幕垂直方向距下边大约三分之一的位置
Helpfile
字符串表达式,识别帮助文件,用该文件为对话框提供上下文相关的帮助
Context
数值表达式,由帮助文件的作者指定给某个帮助主题的帮助上下文编号
其中最重要的参数是前面三个,包括提示信息、标题和默认值。在特殊情况下,第四、五参数也具有其实用价值——强制指定对话框的显示位置,从而防止对话框挡住当前窗口。
或许从以下案例中,可以加深对Inputbox的认识。
1.定制“另存为”对话框
设计一个用于文件另存的对话框,固定保存在C盘下,用户可以随意定制文件名,默认名称为当前日期。代码如下:
Sub 工作簿另存()
Dim FileName As String '声明变量
'弹出一个录入框,让用户指定文件名,默认值为当前日期
FileName = InputBox("请输入工作簿新名称", "另存为", Date)
'当前工作簿另存到C盘中,文件名为用户指定字符
"c:\" & FileName
End Sub
执行以上代码时,将弹出一个“另存为”对话框供用户录入新名称,其默认值为当前日期,见图所示:
图 定制的“另存为”对话框
2.根据指定月份批量创建工作表
用户指定的月份,程序批量创建该月每日日期命名的工作表。代码如下:
Sub 新建工作表() '批量建立新表,个数等于本月天数,同时对日期命名,并建立目录
Dim i As Byte, months As Byte '声明变量
'弹出一个对话框,让用户指定月份,默认显示当前月
months = InputBox("请输入月份,程序将建立该月每日日期命名的工作表", "确定月份", Month(Date))
'批量生成工作表,其个数等于指定月份的天数减去当前已有工作表个数,即确保工作表数量等于该月天数
After:=Sheets(), Count:=Day(DateSerial(Year(Date), months + 1, 0)) -
'将所有工作表重命名,工作表名对应每日的日期
For i = 1 To
Sheets(i).Name = months & "月" & i & "日" '对每个工作表命名
Next i
MsgBox "建立完毕!", 64
End Sub
在以上代码中,Inputbox可以弹出一个对话框,让用户指定月份,默认值为当前月份。而当前月份的计算方式是利用Month函数从当前日期Date中获取。
其中计算用户指定的月份有多少天时,鉴于VBA自动日期转换的特点——将0日当做上月最后一天处理,所以程序利用DateSerial函数将下月0日转换成本月最后一天的日期序列,最后再用Day函数提取其天数,表示当月有多少天。
图是Inputbox函数设置的对话框,让用户指定月份;而图是批量创建的工作表。
图 指定月份的录入框
图 批量创建工作表后的效果
如果在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") & 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之间的任何数字,程序会对应地转换日期为该格式。输入框外观见图所示。
图 提示用户选择转换样式
在该过程中,使用了Choose函数,它可以根据第一参数的值从后面的参数中选择对应的值做为Format的参数。程序没有使用防错功能,如果输入的值小于1或者大于4将产生错误。
本例文件参见光盘:..\ 第十章\将A1日期转换成星期.xlsm
借用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 < 1 Or Months > 12 Then MsgBox "只能在1-12之间,请重新输入。", 64, "提示": Exit Sub
= 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 '居中显示
. = 15 '标示背景色
. = True '加粗显示
End With
With .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(" & (0, 0) & "),"
.Replace What:="y,", Replacement:="YEAR(" & (0, 0) & "),"
.HorizontalAlignment = xlCenter '居中
.Value = .Value '将公式转换成值
. '自动调整列宽
End With
.Resize(8, 7).Borders().LineStyle = xlContinuous '添加边框,中间部分
'再添加外框,外框显示为加粗
.Resize(8, 7).BorderAround ColorIndex:=1, Weight:=xlThick
End With
= True
Exit Sub
endd:
MsgBox "您输入的月份包括文本" & Chr(10) & "或者当前区域无法写入", 65
End Sub
录入代码后返回工作表,通过以下步骤测试代码的可行性:
(1)选择单元格A1,然后按下快捷键【Alt+F8】打开“宏”对话框;
(2)如果工作簿中仅有一个宏,那么Excel会自动选定“生成月历”,否则手工选择宏名“生成月历”并单击“执行”按钮;
(3)此时程序将弹出图所示对话框,提示用户输入日期,其默认值当前月的月份;
(4)输入字母“A”或者输入数字13,那么程序会弹出图所示对话框。因为月份只能是1到12之间的数字;
图 提示录入月份 图 错误提示
(5)如果直接单击“确定“按钮,那么当前工作表中B1开始的7列8行将产生图所示月历:
图 月历
在产生月历前,需要确保当前单元格开始右下方7列8行是否有数据,如果该区域非空,则月历会覆盖该区域中的数据;同时还要确保该区域中不存在合并单元格,因月历无法在合并单元格中录入数组公式。
如果读者认为以上限制太多,也可以变通地实现,突破这两个限制。方法是在当前单元格右方插入新七个空白列。插入的代码如下:
(1, 7). Shift:=xlToRight
其中EntireColumn代表整列,如果仅仅对7个单元格进行插入,那么也只能插入7个单元格;对7列进行插入,则会相应地插入7列。
不过,在本过程中防错仍然非常重要,尽管可以插入7列的来解决前面所提到的问题。因为开发者很难完全预料用户在用在什么情况下使用此命令。例如还有一些较极端的现状,例如用户选择一个图形对象再执行程序,或者当前活动单元格位于工作表最后一行等等。
本例文件参见光盘:..\ 第十章\生成月历.xlsm
Inputbox函数的限制
前面展示了Inputbox函数的功能及其在实际应用的神奇,但也不代表它没有缺陷。它不仅有缺陷,而且较多,要更好地发挥程序的功能、更好地运用好字符输入框,就有必要了解它到底有哪些限制。
整体来讲,Inputbox函数主要有以下限制,制约着它无法更好的在VBA中展现其功能:
1.不能检验用户录入字符的数据类型
通常,在VBA中期望终端用户录入什么类型的数据,但有时终端用户基于测试或者其它目的,会故意录入错误的数据,此时程序可能会弹出与实际错误不相符的提示。而做为开发者,有必要防范此事故发生。
例如图中,要求用户输入1到12的数字,但用户如果故意输入“5月”或者“五”,那么程序会产生错误,且对于此类错误,Inputbox并没有内置任何防范措施。
2.不能让产生单元格引用
如果用户需要输入单元格地址,在Inputbox对话框中,用户只能手工录入地址。这显然效率不高且容易产生错误。例如在Excel 2007使用兼容模式下却手工输入了“ZZ100”之类的错误。而更理想的方式让用户直接选择区域,程序自动将选区地址返回给对话框。
这种问题只能留给Application对象的Inputbox方法来处理。
3.字符长度限制
对话框中的字符串和用户录入的字符(即第一和第三参数)都限制定1024左右,如果是纯汉字则为511个。
如果用户的需求超过以上限制,应该考虑使用窗体来完现。
利用方法替代Inputbox函数
VBA中有一个比Inputbox函数功能类似,却强大很多的语句——方法。
方法在功能上与Inputbox函数基本一致,但却提供了数据类型检测和直接产生区域引用的功能,大大方便用户的使用。
在工作中尽量使用方法替代Inputbox函数。相对于Inputbox函数,方法在用法上基本一致,只是多了一个指定数据类型的参数,却在使用中提供了很多的便利。
使用中一定要要注意,不对用户录入信息进行验证的是VBA中的Inputbox函数,带验证功能的是方法。
语法详解
方法的基本语法如下:
(Prompt, Title, Default, Left, Top, HelpFile, HelpContextID, Type)
表10-5中包括了方法的各参数详解。
表10-5 方法的各参数详解
名称
必选/可选
描述
Prompt
必选
要在对话框中显示的消息。可为字符串、数字、日期、或布尔值(在显示之前, Excel自动将其值强制转换为String)
Title
可选
输入框的标题。如果省略该参数,默认标题将为“Input”
Default
可选
指定一个初始值,该值在对话框最初显示时出现在文本框中。如果省略该参数,文本框将为空。该值可以是 Range 对象
Left
可选
指定对话框相对于屏幕左上角的 X 坐标(以磅为单位)
Top
可选
指定对话框相对于屏幕左上角的 Y 坐标(以磅为单位)
HelpFile
可选
此输入框使用的帮助文件名。如果存在 HelpFile和HelpContextID 参数,对话框中将出现一个帮助按钮
HelpContextID
可选
HelpFile 中帮助主题的上下文 ID 号
Type
可选
指定返回的数据类型。如果省略该参数,对话框将返回文本
方法有8个参数,其中最重要的是前三个和最后一个。Type参数可以指定种或者多种数据类型,而方法则会根据该类型对用户的录入信息进行检查,如果不符合指定类型则会阻止程序执行。
现对第八个参数在工作中的应用展示三个案例。
1.强制用户录入数值
以图中提示用户录入月份为例,强制用户录入1到12月的数值,否则程序拒绝执行。方法完全可以胜任,代码如下:
Months = ("请指定月份,程序将生成该月的月历", "月份", Month(Date), 10, 10, , , 1)
将以上代码替换“生成月历”中对应的代码可即,其中最后一个参数1表示只能录入数值。
当用户执行代码时,如果在对话框中录入非数值“5月”,那么程序将提示“无效的数字”,见图所示:
图 录入非数值时的提示
2.对任意选区进行行列合计
以本书第七章中案例二为例,该代码的缺陷在于必须运行程序前选择正确的区域,否则将产生错误结果,而这显得灵活度不足。借用方法可以弥补这个缺陷。
修改后的代码如下:
Sub 行列自动合计()
Dim rng As Range, address As String '声明一个对象变量
'如果当前选择的对象是单元格则将单元格地址赋与变量,否则将空文本赋与变量
IF TypeName(Selection) = "Range" Then address = Else address = ""
'弹出一个对话框,让用户选择区域,默认显示变量address的值。然后将该用户选择区域赋与变量rng
Set rng = ("请选择待合计的区域", "合计区域", address, , , , , 8)
IF rng Is Nothing Then Exit Sub
'先汇总各行的值
For i = 1 To '从1到总行数
'利用Offset取得汇总数据的放置位置,即选区第一个单元格向右偏移选区的列数
'合计区域也用Offset逐行偏量来获取,Resize的作用是重置为1行,否则会汇总其它行的数据
rng(1).Offset(i - 1, ) = ((i - 1).Resize(1))
Next
'再汇总各列的值
For i = 1 To + 1 '从1到总列数加1,因为需要对行的汇总数再进行汇总
rng(1).Offset(, i - 1) = ((, i - 1).Resize(, 1))
Next
End Sub
在本过程中,相对于的代码加入了选区对象检测及语句。选区检测是为了获取选择对象的地址,并将该地址做为输入框中的默认值,它给用户提供一些便利。如果用户当前选区正是待计算区域,那么可以直接执行运算;如果当前区域非待计算区域时,则可手动选择目标区域。这相对原有代码更具人性化。
方法的第八参数使用8,表示返回单元格引用,其数据类型为Range。
在执行本过程时,不强制要求用户选择待计算区域,可以在执行程序后再确定区域。图即为手工拖动鼠标选择区域时,对话框相应地产生区域地址:
图 通过鼠标拖动录入区域地址
本例文件参见光盘:..\ 第十章\任意选区行列自动求和.xlsm
3.利用录入公式
在单元格中录入公式时,Excel会对公式进行检查,如果不符合公式的基本语法会阻止用户录入。而VBA中的Inputbox方法也可以实现同等功能。
例如对图中的数据进行排名次,如果使用VBA的对话框来录入公式,那么代码如下:
Sub 设置计算名次的公式()
'首先选择待输入公式的单元格
[c2].Select
'设置C2的公式,第8参数必须用0,否则单元格中显示值而非公式
[c2].FormulaLocal = ("请输入计算名次的公式:", "公式", , , , , , 0)
'填充公式
Range("C2").AutoFill Destination:=Range("C2:C" & Cells(, 2).End(xlUp).Row)
End Sub
该过程中,第八个参数使用0,表示在C2单元格产生公式,如果使用其它值做为参数则只能产生公式的结果,而非公式本身。
利用方法录入公式需要注意四点:
(1)在弹出对话框前必须先定位于目标单元格,否则公式中引用的单元格或者区域会产生错位,类似于条件格式中的引用;
(2)在对话框中录入公式时,可以利用鼠标单击单元格来产生地址,而且可以通过快捷键【F4】使其在相对引用、绝对引用与混合引用三个状态之间切换,和直接在单元格中录入公式的方式一致;
(3)在代码中必须对存放公式的单元格使用FormulaLocal属性,那么VBA就会对录入的公式进行检测,如果录入的字符不符合公式的格式,那么将阻止程序继续执行,从而确保公式的正确性;
(4)如果需要在单元格中录入数组公式,则需要使用FormulaArray 属性。代码如下:
[c2].FormulaArray = ("请输入计算名次的公式:", "公式", , , , , , 0)
执行本过程时,VBA会弹出一个输入公式的对话框,在其中录入公式“=rank(B2,$B$2:$B$8)”然后单元格C2会自动产生公式,且将公式向下填充,直到B列最后一个非空单元格。
图 待排名次的成绩表 图 在对话框录入排名次的公式
如果在其中录入一个不完整的公式“=rank(B2,$B$2:$B$8”,那么VBA会提示用户公式缺少括号,见图所示。
图 公式缺少括号时弹出提示框
如用户在对话框中录入公式时忽略了等号,那么VBA将它当做文本字符串,自动添加引号及等号。例如用户录入“rank(B2,$B$2:$B$8)”,那么单元格中则会产生以下公式:
="rank(B2,$B$2:$B$8)"
方式录入的公式可以产生有看不见的工作表中,这是相对手工录入公式优越性。例如sheet2属于隐藏状态,那么以下语句完全不影响正常执行,仍然可以在目标单元格产生正确公式:
Sheet2.[c2].FormulaLocal = ("请输入计算名次的公式:", "公式", , , , , , 0)
本例文件参见光盘:..\ 第十章\ Inputbox方法录入公式.xlsm
条件判断语句
条件判断语句在VBA中也是使用率非常高的语句。
用户在Excel中录制宏时无法如何都无法产生条件语句,必须通过VBE界面手工编写代码,那么了解它的语法就显得犹为重要了。
条件语句主要包括以下五种:
IIF
IF…Then…
IF…Then…End IF
Select Case…End Select
Choose
本节将对以上五种条件语句进行详述。
IIF函数的语法与应用
IIF函数是VBA中类似于工作表IF的条件函数,它基于条件返回不同的值。下面展示它的参数及用法。
的参数
IIF函数可以根据表达式的值,来返回两部分中的其中一个。它的基本语法为:
IIF(expr, truepart, falsepart)
IIF的三个参数均为必选参数,各参数的含义见表10-6所示:
表10-6 IIF的参数详解
部分
描述
expr
用来判断真伪的表达式
truepart
如果 expr 为 True,则返回本参数的值或表达式
falsepart
如果 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)
运算符与Or运算符
当IIF函数使用多条件时,必须借助And运算符与Or运算符来连接其条件。
如果需要同时满足多条件时,可使用And运算符。And运算符的主要作用是对两个表达式进行逻辑连接。表达式如下:
result = expression1 And expression2
其中result与expression1、expression2之间的关系见表10-7所示:
表10-7 And运算符参数与结果之关系
如果 expression1 为
且 expression2 为
则 result 为
TRUE
TRUE
TRUE
TRUE
FALSE
FALSE
TRUE
Null
Null
FALSE
TRUE
FALSE
FALSE
FALSE
FALSE
FALSE
Null
FALSE
Null
TRUE
Null
Null
FALSE
FALSE
Null
Null
Null
如果有三个条件可以采用以下表达式:
result = expression1 And expression2 And expression3
如果某行业招聘时要求体重在50到65公斤之间合格,否则不合格,那么VBA表达方式如下:
Msgbox IIF([a1] > 50 And [a1] < 65, "合格", "不合格")
如果需要多条件中满足条件之一即可返回指定值,那么可以使用Or运算符,其主要作用是对两个表达式进行逻辑析取运算。其表达式如下:
result = expression1 And expression2
其中result与expression1、expression2之间的关系见表10-8所示:
表10-8 Or运算符参数与结果之关系
部分
描述
result
必需的;任何数值变量
expression1
必需的;任何表达式
expression2
必需的;任何表达式
如果成绩小于1或者大于100则提示“录入错误”,否则返回空值,那么VBA表达方式如下:
MsgBox IIF([a1] > 100 Or [a1] < 1, "录入错误", "")
IIF函数也可以嵌套使用,即一句代码中使用多个IIF,根据两个以上的条件返回对应的值,而且每个条件参数也可以借用And或者Or运算符来连接。
当And和Or共用一个参数的时候,尽量采用括号来体现优先顺序。例如
(expression1 And expression2) Or (expression3 And expression4)
应用案例
实例一:根据录入的月份计算季度
利用录入框让用户录入月份,默认为当前月,并根据月份判断其季度。代码如下:
Sub 根据月份判断季度()
Dim Months As Byte '声明变量
Star: '设置一个标签
'弹出对话框让用户录入月份,默认为当前月份
Months = ("请输入月份,只能是数字", "月份", Month(Date), , , , , 1)
'如果录入的数值小于1或者大于12则返回标签Star处继续执行
IF Months < 1 Or Months > 12 Then MsgBox "只能在1到12之间": GoTo Star
'四个IIF嵌套运用,其中每个IIF的第一参数使用双条件,在双条件时需要用And连接
MsgBox IIF(Months > 1 And Months < 4, "一季度", IIF(Months > 3 And Months < 7, "二季度", IIF(Months > 6 _
And Months < 10, "三季度", IIF(Months > 9 And Months < 1, "四季度", "录入错误"))))
End Sub
该过程中InputBox方法第八参数使用1,代表强制用户录入数字,这是极有必要的措施。再用VBA中的IF语句配合Or运算符检查录入数值是否在1到12之间,如果不在该范围则提示,且返回“Satr”标签处,让用户继续输入数值,直到符合条件完止。
在过程中,使用了四个IIF函数嵌套,且四个IIF函数的第一个条件都包括两个参数,利用And进行连接。
在执行程序时,将弹出一个录入数字的对话框,其默认值为当前月份,见图所示。如果此时录入0或者13将会提示“只能输入1到12间”,且再次返回月份录入对话框等待用户录入正确的数字。当输入数字5后,返回结果见图所示。
图 月份录入框 图 根据月份判断季度
本例文件参见光盘:..\ 第十章\根据月份判断季度.xlsm
实例二:多条件计算奖金
某公司对业务员的奖金计算方式为:业绩高于12万,其利润比例在15%以上者奖金为2000元;业绩高于8万、利润12%以上但劳当选为劳模者也可以奖金2000元,其他人奖金1000元。
利用VBA对图所示业务员计算奖金之代码如下:
Sub 计算奖金()
Dim i As Byte
'从2开始至最后一行个非空行结束
For i = 2 To ([B:B])
'根据要求设置条件
Cells(i, "E") = IIF((Cells(i, "B") > 12 And Cells(i, "C") > ) Or ((Cells(i, "B") > 8 _
And Cells(i, "C") > ) And Cells(i, "D") = "是"), 2000, 1000)
Next i
End Sub
该过程中And运算符与Or运算符合用,显得较复杂,但可以顺利地完成需求。读者需要将代码与题目要求进行详细的对比,理解代码的设计思路。
图 奖金计算表
本例文件参见光盘:..\ 第十章\多条件计算奖金
实例三:利用IIF计算当前Office版本
目前用户最常用的三个OFFICE版本分别是Office 2002、2003和2007,以下程序可以返回OFFICE的版本。代码如下:
Sub Office版本()
MsgBox "Excel 200" & IIF( = 10, "2", IIF( = 11, 3, IIF( = 12, 7, "")))
End Sub
IIF函数的应用极广,用户可以自己多多测试。
提示:条件语句永远无法在录制宏中产生,读者只能编过手工编写,从实践中摸索条件语句的使用技巧
IIF函数的限制
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函数。
IF… 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"). = True: Range("a1"). = True
IF… Then…语句句式的条件语句与IIF函数中的条件一样也可以使用And和Or运算符来连接多个条件。And与Or运算方参见表10-7与表10-8。
IF…then…应用案例
现通过几个案例展示IF…Then…句式的用法,让读者对它有进一步的认识。
1.禁止打印“总表”以外的工作表
工作簿中有很多工作表,禁止用户打印工作表以外的任何工作表数据,实现步骤如下:
(1)按下快捷键【Alt+F11】进入VBE界面;
(2)如果未显示工程资源管理器,则使用快捷键【Ctrl+G】显示工程资源管理器。然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在窗口中录入以下事件过程代码:
'声明工作簿打印事件
Private Sub Workbook_BeforePrint(Cancel As Boolean)
'如果工作表名不等于“总表”就禁止打印
IF <> "总表" Then MsgBox "禁止打印": Cancel = True
End Sub
(4)返回工作表界面,选择任意工作表并单击“打印”,将弹出所示提示,同时禁止工作表打印。
图 打印总表工外的工作表时的提示信息
在该事件过程中,“Cancel”参数用于控制是否可以打印,对当前工作簿中所有工作表都生效,但当前要求是允许打印“总表”,所以必须借助IF语句排除“总表”,对当前工作表名不等于“总表”者进行限制。
在本例的条件语句中,符合条件时需要执行两句代码,包括提示及禁止打印,那么两句代码之间必须使用冒号分隔,且必须写在同一行中。
本例文件参见光盘:..\ 第十章\禁止打印总表以外的工作表.xlsm
2.仅仅允许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程序
End Sub
(4)保存工作簿后再重新开启,如果当前时间小于8点钟或者大于18点钟,那么工作簿会自动关闭。
在该过程中,IF条件语句中有两个条件,只要满足条件之一即退出程序,所以借用Or运算符来连接条件。也可以改用And运算符,那么重编代码如下:
'声明工作簿开启事件
Private Sub Workbook_Open()
'如果现在的小时数大于等于8,而且小于等于18则退出Excel程序
IF Hour(Now) < 8 And Hour(Now) > 18 Then
End Sub
在本过程中,与前一个过程可以实现完全相同的功能,只不过对于条件的处理方式不同。
本例文件参见光盘:..\ 第十章\允许8到18点开启工作簿.xlsm
3.如果A1是数字,则当前工作簿保存为A1的值
将当前工作簿另存,且文件以A1的值命名,但A1非数字时例外。实现步骤如下:
(1)按下快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【模块】,在模块代码窗口中输入以下代码:
Sub 工作簿另存()
'如果A1是数字,而且非空,那么工作簿保存为A1的值
IF ([a1]) And Len([a1]) > 0 Then "C:\" & [a1], FileFormat:=xlOpenXMLWorkbookMacroEnabled
End Sub
(3)返回工作表界面,在A1单元格输入“5月1日”,然后按下快捷【Alt+F8】打开“宏”对话框,选择“工作簿另存”并执行过程,因为A1不是数值而不会有任何反应;
(4)A1单元格清除格式,并输入2005,再次执行过程,工作簿立即被保存为“”,且存于C盘根目录。
在本过程中,除了需要判断A1是否是数字外,还非常有必要判断其是否非空。因为假设A1单元格是空白,那么IsNumeric函数会将其当作0参与计算,则条件为True,而事实上A1单元格是空时需要终止程序继续执行,所以利用And连接两个条件,从而精确地判断A1是否符合条件。
另一点需要注意的是,工用簿另存时不指定文件类型会以Xlsx格式保存,而带有宏的工作簿需要以Xlsm格式才能将宏代码保存下来,所以在代码中需要使用“FileFormat”参数指定文件格式。
本例文件参见光盘:..\ 第十章\以A1的值为文件名另存工作簿.xlsm
IF…Then…Else…语法与应用
IF…Then…语句用于满足条件时执行指定的语句,在实际工作中无法满足更多工作需求。IF…Then…Else…语句也根据表达式的值有条件地执行一组语句,然而相对于IF…Then…语句它还可以指定在满足条件时的需执行的语句,其应用范围更广。
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.实例:多条件计算奖金
例如据图所示的数据计算提金额,条件为业绩大于12万且利润比例大于15%时提成金额为2000元,否则为1000元,两种解法如下:
图 业绩表
Sub 计算奖金1() '同行
Dim i As Byte
'从2开始至最后一行个非空行结束
For i = 2 To ([B:B])
'根据要求设置条件:如果满足条件,提成2000,否则提成1000
IF (Cells(i, "B") > 12 And Cells(i, "C") > ) Then Cells(i, "D") = 2000 Else Cells(i, "D") = 1000
Next i
End Sub
Sub 计算奖金2() '不同行
Dim i As Byte
'从2开始至最后一行个非空行结束
For i = 2 To ([B:B])
'如果满足条件,那么
IF (Cells(i, "B") > 12 And Cells(i, "C") > ) Then
Cells(i, "D") = 2000 '奖金为200
Else '否则
Cells(i, "D") = 1000 '否则奖金为1000
End IF '结束条件语句
Next i
End Sub
两个过程的执行结果完全一致,但在代码阅读和理解上前一个过程更方便,也显得更简洁。然而在执行语句较多时,却完全相了,多行句式会有占有优势。
本例文件参见光盘:..\ 第十章\多条件计算奖金
再如,单元格A1的值大于60时,将A1的字体加粗倾斜,并添加红色背景。也有两种解法,见图所示:
图 两种条件语句写法的比较
从图进行比较,可以看到当有多行语句时,将IF…Then…Else…条件语句写为多行会显示更有条理性和层次感。
条件语句的嵌套应用
条件语句用工作表函数IF一样,可以多层嵌套,从而满足多条件的需求。
1.语法详解
条件语句的嵌套也分两种方式,现分别进行介绍如下。
方式一
IF Condition Then
[statements]
[ElseIF condition-n Then
[elseifstatements] ...
[Else
[elsestatements]]
End IF
语法列表中省略号代表可以继续添更多的条件和对应的执行语句。
例如:根据图所示的成绩对姓名所在单元格进行着色,如果成绩小于60则填充灰色背景,如果在60(含)到100之间则填充淡蓝色背景,如果100分则填充红色背景。完成这个需求可以利用条件语句嵌套完成,代码如下:
Sub 根据成绩设置背景色()
'遍历所有成绩
For i = 2 To Cells(, 2).End(xlUp).Row
'如果成绩小于60 第一层IF
IF Cells(i, "B") < 60 Then
'单元格颜色地址设置为15
Cells(i, "A"). = 15
'如果成绩小于100(相对于60到100但不包括100),第二层IF
ElseIF Cells(i, "B") < 100 Then
'单元格颜色地址设置为17
Cells(i, "A"). = 17
Else '否则
'单元格颜色地址设置为13 ,代表满分100
Cells(i, "A"). = 3
End IF
Next i
End Sub
以上过程中IF语句设置条件为“<60”,然后将其背景色设置灰色;而ElseIF语句则是在前一个IF的基础上进行的判断,所以它的条件虽然是“<100”,而实际上是大于等于60且小于100这个范围;最后的Else语句则作用于100分成绩对应的单元格。
执行过程后,其效率见图所示:
图 成绩表 图 根据成绩对姓名列着色
本例文件参见光盘:..\ 第十章\根据成绩设置背景色.xlsm
方式二
IF Condition Then
IF Condition Then
[statements]
[Else
[elsestatements]]
End IF
End IF
本嵌套方式表示即在“IF”与“End IF”之间置入若干个“IF…Then…Else…”条件语句,那么有多少个“IF”就会有多少个“End IF”。
使用此类语句通常是指要满足某件后再执行一个或多个执行条件语句。里面的条件语句既可使用条件与执行语句在同行的句式,也可使用条件与执行语句不在一行的句式。
2.实例:模防复选框控制单元格
复选框可以实现单击时打勾,再次单击打叉。但在打印时却有一个方框。如果需在将勾与叉打印出来,又不能出现方框,那么可以利用VBA事件,配合条件来实现。以图为例,在B列利用勾与叉来体现产品是否合格,具体步骤如下:
图 产品检验表
(1)在工作表标签处单击右键,选择菜单【查看代码】,从而进入工作表代码窗口;
(2)在窗口中录入以下代码:
'声明事件过程 SelectionChange事件
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'如果当前单元格在B列
IF Not (Target, Range("B:B")) Is Nothing Then
'如果仅仅选择一个单元格
IF = 1 Then
'如果当前活动单元格空白或者"×"
IF Len(Target) = 0 Or Target = "×" Then
'那么在单元格写入“√”
Target = "√"
Else '否则
'写入“×”
Target = "×"
End IF '结束最里层IF语句
End IF '结束第二层IF语句
End IF '结束最外层IF语句
End Sub
(3)返回工作表界面,单击B列以外的任意单元格,不会产生任何反应,因为过程代码中外层的IF已做限制;
(4)在B列的任意单元格单击,如果单元格原来是空单元格,或者有一个符号“×”,那么将产生一个“√”;如果原本有一个符号“√”则产生符号“×”。而选择多个单元格时则不会有任何反应;
(5)以上代码基本上实现了勾与叉的切换,然而有个问题却困扰大家——在当前活动单元格单击时不会触发工作表事件,那么意味着当前单元格的勾与叉无法即时切换,只能单击其它单元格后再回到当前活动单元格才能完成切换,这显然比复选框的方便性大大不足。为了解决这一BUG,可以采用双击事件来处理,即在当前单元格中双击切换符号“√”与“×”。具体代码如下:
Dim rng As String '声明一个公共变量
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'如果当前活动单元格的地址等于变量的地址(即双击活动单元格才触发本事件)
IF = rng Then
'如果当前活动单元格空白或者"×"
IF Len(Target) = 0 Or Target = "×" Then
'那么在单元格写入“√”
Target = "√"
Else '否则
'写入“×”
Target = "×"
End IF '结束最里层IF语句
End IF
End Sub
'声明事件过程 SelectionChange事件
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'如果当前单元格在B列
IF Not (Target, Range("B:B")) Is Nothing Then
'如果仅仅选择一个单元格
IF = 1 Then
'将当前活动单元格地址赋与变量Rng
rng =
'如果当前活动单元格空白或者"×"
IF Len(Target) = 0 Or Target = "×" Then
'那么在单元格写入“√”
Target = "√"
Else '否则
'写入“×”
Target = "×"
End IF '结束最里层IF语句
End IF '结束第二层IF语句
End IF '结束最外层IF语句
End Sub
将单击与双击事件配合使用后,基本可以实现复选框的功能了。
在单击事件时,使用了IF限制事件只在单击单个单元格是触发,而在双击事件中不需要进行限制。因为双击事件本身就只能作用于单个单元格,事件过程中的参数Target只能引用一个单元格。
在本过程中,使用了IF的多层嵌套,限制符号“×”和“√”只在指定的条件下才会产生或者切换,这是条件语句的独特作用。
本例文件参见光盘:..\ 第十章\单击录入勾与叉.xlsm
Select Case语法详解
Select Case语句也是条件语句之一,而且是功能最强大的条件语句。它主要用于多条件判断中,而且其条件设置灵活、方便,在工作中使用频率极高。
1.语法详解
Select Case语句的语法如下:
Select Case testexpression
[Case expressionlist-n
[statements-n]] ...
[Case Else
[elsestatements]]
End Select
Select Case语句包括四部分,每部分详细含义见表10-9:
表10-9 Select Case语句各部分含义
部分
描述
testexpression
必要参数。任何数值表达式或字符串表达式
expressionlist-n
如果有 Case 出现,则为必要参数。其形式为 expression,expression To expression,Is comparisonoperator。expression的一个或多个组成的分界列表。To 关键字可用来指定一个数值范围。如果使用 To 关键字,则较小的数值要出现在 To 之前。使用 Is 关键字时,则可以配合比较运算符(除 Is 和 Like 之外)来指定一个数值范围。如果没有提供,则 Is 关键字会被自动插入
statements-n
可选参数。一条或多条语句,当testexpression匹配expressionlist-n中的任何部分时执行
elsestatements
可选参数。一条或多条语句,当testexpression不匹配Case子句的任何部分时执行
在以上语法列表中,省略号代表可以使用多个条件。只要有一个Case就需要有一个statements-n,表示条件及符合条件时的执行条件。
其中elsestatements表示不符合指定条件时的执行语句,是可选参数。可以忽略elsestatements,也可以执行一条或者一组语句,为了让程序能够处理一些不可预见的情况,尽量使用elsestatements语句用于处理不符合条件时该如何回应。
在Select Case的多个参数中,最复杂的是expressionlist-n部分,它有多种表达形式,包括:
Expression——直接声明一个条件值,例如5
expression To expression——声明一个条件的范围,例如5-10
Is comparisonoperator——声明一种比较方式,例例is >5
下面的实例可以展示参数中expressionlist-n部分的多种表达形式。
2.实例:多条件时间判断
根据当前的小时判断是上午、中午,还是下午、晚上、午夜。
要求中条件比较多,使IF…Then…需要多层嵌套,而Select Case语句会更简单。代码如下:
Sub 时间()
Dim Tim As Byte, msg As String
Tim = Hour(Now)
Select Case Tim
Case 1 To 11
msg = "上午"
Case 12
msg = "中午"
Case 13 To 16
msg = "下午"
Case 17 To 20
msg = "晚上"
Case 23, 24
msg = "午夜"
End Select
MsgBox "现在是:" & msg
End Sub
以上代码中,“Case 1 To 11”表示当前时间在1点到11点,用于限定一个范围;“Case 12”是表示当前时间为12点时返回真,可用于限制一个具体的值,或者多个值。如果需要罗列多个具体的值,那么需要在每个值之间使用逗号分隔,例如“Case 23, 24”。
本例中不存在例外的情况,所以忽略“Case Else”语句。
3.实例:根据成绩返回评语
如果成绩小于60则返回“不及格”,60到80之间则返回“良”,80到99则返回“优”,100分则返回“满分”,如果成绩大于100则是输入了错误值。利用一个Function过程来处理,其代码如下:
Function 成绩(rng As Range)
Select Case rng
Case Is < 0, Is > 100
成绩 = "输入错误"
Case Is < 60
成绩 = "不及格"
Case 60
成绩 = "及格"
Case 60 To 80
成绩 = "良"
Case 81 To 99
成绩 = "优"
Case Else
成绩 = "满分"
End Select
End Function
以上代码中,Is关键词用于指定一种比较方式,后接Is和Like以外的比较运算符。本例中需要限制小于0和大于100两个条件,那么Is关键词可以同行中使用两次,中间用逗号分隔,表示罗列两个条件。
“Case Is < 60”语句在第一个条件“Case Is < 0, Is > 100”基础上再进行比较,那么成绩“-10”将不符合“<60”这个条件,因为 Select Case在处理多条件时,总是按比上到下的顺序处理。如果改变本例条的条件顺序,可能产生不同的运算结果。
“Case Else”语句表示如果不符合前面的所有条件,则执行它后面的语句,本例中表示成绩为100时返回“满分”。
在工作表中使用该自定义函数后,结果见图所示。
图 利用Function返回成绩评语
本例文件参见光盘:..\ 第十章\根据成绩返回评语.xlsm
4.实例:以指定格式的今日日期显示工作簿标题
Excel的标题默认显示“Microsoft Excel”,现要求显示为今日日期,且日期需要大小写三种方式供用户选择。具体代码如下:
Sub 以今日日期显示标题()
Dim str As String
star:
Select Case ("请指定日期显示方式:" & Chr(10) & "1:数字日期" & Chr(10) _
& "2:中文小写" & Chr(10) & "3:中文大写", "日期显示方式", 1, , , , , 1)
Case 1
str = "yyyy-mm-dd"
Case 2
str = "[DBNum1] yyyy年mm月dd日"
Case 3
str = "[DBNum2] yyyy年mm月dd日"
Case Else
MsgBox "录入错误,请重新录入"
GoTo star
End Select
= (Date, str)
End Sub
本例是“Select Case”对象不确定的实例,它在执行后由用户手动指定对象。
在Select Case语句中,利用Case限制了三个条件,分别为手动录入1、2和3时的日期显示格式。但用户可能会误输入1到3范围以外的数值,使程序产生错误,那么利用Case Else语句来限制,且返回InputBox语句处让用户重新录入,直到数值处于要求的范围内为止,这是最理想的处理方式。
本例文件参见光盘:..\ 第十章\以指定格式的今日日期显示工作簿标题.xlsm
5.实例:Select Case嵌套应用计算时间
和IF…Then…一样,Select Case也可以多层嵌套。本例根据现在的小时数判断白天或者晚上,而如果是白天,再进行细分“上午”、“正午”和“下午”。详细代码如下:
Sub 时间()
tim = Hour(Now) '获取现在的时间
Select Case tim '条件语句开始,第一层
Case 8 To 18 '如果是白天
Select Case tim '嵌套使用,现二层
Case Is < 12 '如果于12
MsgBox "上午"
Case 12 '如果等于12
MsgBox "正午"
Case Else '其它的情况,表示13到18
MsgBox "下午"
End Select '结束时层条件语句
Case Else '其它的情况,外层
MsgBox "晚上" '表示1到8之间及19到24
End Select
End Sub
在Select Case语句第一个条件中嵌套了一个Select Case条件语句。根据需要,还可以嵌套多层条件语句。
Select 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 And [b1] > 60 Then
Call A
Else
Call B
End IF
End Sub
而Select Case语句是无法完成以上条件的,它只能完成A1大于60或者A1小于60时执行过程“A”,否则执行过程“B”这种需求。代码如下:
Sub 单一对象多条件() 'Select Case
Select Case [a1]
Case Is < 60, Is > 60
Call a
Case Else
Call B
End Select
End Sub
在Select Case条件语中,只能对一个对象设置条件,当可能是多个条件。但多个对象指定多上条件则只能改用IF…Then…Else语句。
Select Case的优势在于同一对象设置多条件时,它的速度优于IF…Then…Else语句。IF...Then...Else 语句会计算每个 ElseIF 语句的不同的表达式,在控制结构的顶部,Select Case语句只计算表达式一次。
例如以下两个过程中,达成的结果完全一致,但在速度上有一定差异。其中Select Case语句只需要判断一次变量“Select Case”符合哪一个条件,而在IF…Then…Else语句中,有多个少IF和ElseIF就需要判断多少次。
Sub 评语1()
Select Case 成绩
Case Is < 0, Is > 100
MsgBox "输入错误"
Case Is < 60
MsgBox "不及格"
Case 60
MsgBox "及格"
Case 60 To 80
MsgBox "良"
Case 81 To 99
MsgBox "优"
Case Else
MsgBox "满分"
End Select
End Sub
Sub 评语2()
IF 成绩 < 0 Or 成绩 > 100 Then
MsgBox "输入错误"
ElseIF 成绩 < 60 Then
MsgBox "不及格"
ElseIF 成绩 = 60 Then
MsgBox "及格"
ElseIF 成绩 > 60 And 成绩 < 80 Then
MsgBox "良"
ElseIF 成绩 > 80 And 成绩 < 100 Then
MsgBox "优"
Else
MsgBox "满分"
End IF
End Sub
根据以上分析,读者可以进行多次测试,在实际工作中根据需求选择适当的语句。
借用Choose函数简化条件选择
Choose函数的作用是从参数列表中选择并返回一个值,它是一个函数,只能返回值,不能根据条件执行指定的过程或者语句,是类似于IIF函数,而有别于IF…Then…语句和Select Case语句。
Choose函数的基本语法如下:
Choose(index, choice-1[, choice-2, ... [, choice-n]])
其中index必选参数,可用数值表达式或字段做参数,它的运算结果是一个数值,且界于1和可选择的项目数之间。如果超过可选择项目个数将产生错误结果。
除Index以外的所有参数是可选项目,可选项目中第一个为必选参数,其余为可选参数。
Index参数必须使用数值,或者结果为数值的表示式,函数将从可选项目中返回Index对应的值。例如:
Choose(3, 1, 2, 3, 4)——返回3,从项目表中返回第三个
Choose((3 + 2) / , "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
颜色 = ("请选择颜色:" & Chr(10) & "1:红色" & " 2:蓝色" & Chr(10) & _
"3:灰色" & " 4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)
Range("A1"). = Choose(颜色, 7, 5, 15, 40, 4)
End Sub
Sub 设置单元格颜色2() 'IIF法
Dim 颜色 As Byte
颜色 = ("请选择颜色:" & Chr(10) & "1:红色" & " 2:蓝色" & Chr(10) & _
"3:灰色" & " 4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)
Range("A1"). = IIF(颜色 = 1, 7, IIF(颜色 = 2, 5, IIF(颜色 = 3, 15, IIF(颜色 = 4, 40, IIF(颜色 = 5, 4, xlNone)))))
End Sub
Sub 设置单元格颜色3() 'IF...Then...Else法
Dim 颜色 As Byte
颜色 = ("请选择颜色:" & Chr(10) & "1:红色" & " 2:蓝色" & Chr(10) & _
"3:灰色" & " 4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)
IF 颜色 = 1 Then
Range("A1"). = 7
ElseIF 颜色 = 2 Then
Range("A1"). = 5
ElseIF 颜色 = 3 Then
Range("A1"). = 15
ElseIF 颜色 = 4 Then
Range("A1"). = 40
ElseIF 颜色 = 5 Then
Range("A1"). = 4
End IF
End Sub
Sub 设置单元格颜色4() 'Select Case法
Dim 颜色 As Byte
颜色 = ("请选择颜色:" & Chr(10) & "1:红色" & " 2:蓝色" & Chr(10) & _
"3:灰色" & " 4:棕色" & Chr(10) & "5:绿色", "指定颜色", 1, , , , , 1)
Select Case 颜色
Case 1
Range("A1"). = 7
Case 2
Range("A1"). = 5
Case 3
Range("A1"). = 15
Case 4
Range("A1"). = 40
Case 5
Range("A1"). = 4
End Select
End Sub
执行以上任意程序时,将弹出图所示对话框,如果录入1到5之间任何数值,那么单元格A1会产生对应的颜色。
图 要求用户指定A1需要显示的颜色
以上四个过程可以产生同样的效果,但从代码的简捷性上看,Choose函数处理的方式最好。
本例文件参见光盘:..\ 第十章\按指定值设置单元格背景色.xlsm
不过Choose函数也有它的限制。
Choose函数进行条件选择时,虽然它只返回一个选项值,但Choose会计算列表中的每个选择项。这一个副作者类似于IIF函数。
或许从以下实例中更能理解这个副作用:
Choose(2, 12 + 2, 356, 12 / 0)
代码中Index参数为2,只取第二个选项列表中第二个值,但是Choose会将每个选项计算一次,任意一个参数出错则会整个过程中断。见图所示。
图 因除数为零而程序中断
如果改用Select Case语句则不存在此副作用,见以下代码:
Sub 选择性取值()
a = 2
Select Case a
Case 1
MsgBox 12 + 2
Case 2
MsgBox 256
Case 3
MsgBox 12 / 0
End Select
End Sub
循环语句
循环语句(又称控制结构),它可能重复执行一系列代码,从而批量地完成工作任务。循环语句在实际工作中应用极广,且因为循环语句不可能利用录制宏产生,所以必须潜心掌握它的所语法与结构。
循环语句主要包括以下几类:
For Next
For Each Next
Do Loop
Do While Loop
For Next语句
在工作中,我们可以使用 For...Next 语句去重复一个语句块,它的循环次数可以自由指定,循环的步骤也可以自由指定。
1.法语详解
For Next循环语句的基本语法如下:
For counter = start To end [Step step]
[statements]
[Exit For]
[statements]
Next [counter]
语法列表中包括五个部分,其参数详解如下:
表10-10 For Next语法详解
部分
描述
counter
必要参数。用做循环计数器的数值变量。这个变量不能是 Boolean 或数组元素
start
必要参数。counter 的初值
End
必要参数,counter 的终值
Step
可选参数。counter 的步长。如果没有指定,则 step 的缺省值为 1
Statements
可选参数。放在 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 Sums
End 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 Item
'报告最后结果
MsgBox Sums
End Sub
此过程与上一个过程执行结果完全相同,仅仅在写法上不同。
2.步长值正负对过程的影响
在前面的实例中,步长值正负都取了相同的结果。但是在某些特情况下,步长为正数还是负数对结果有很大的影响。
例如通过循环来删除工作表中前20行(仅演示用),那么使用正负步长会产生截然不同的结果,比对步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中录入以下两段代码:
Sub 循环删除前20行A()
'声明变量
Dim Item As Integer, Sums As Integer
'指定步长和循环的起止范围
For Item = 1 To 20
'逐行删除(也就是删除第一到第二十行)
Cells(Item, 1).
'执行下一个
Next Item
End Sub
Sub 循环删除前20行B()
'声明变量
Dim Item As Integer, Sums As Integer
'指定步长和循环的起止范围
For Item = 20 To 1 Step -1
'逐行删除(也就是删除第一到第二十行)
Cells(Item, 1).
'执行下一个
Next Item
End Sub
(3)返回工作表界面。为了查看删除效果,在A1:B1单元格分别输入1和2,,然后将其填充到A30,使A1:A30区域产生1到30的序列号;
(4)按下快捷键【Alt+F8】打开“宏”对话框,首先执行第一个过程,执行结果见图所示;
(5)再执行第二个过程,其执行结果见图所示:
图 步长为正数的执行效果 图 步长为负数的执行效果
从以上两个程序的执行效果比较可以看到步长的正负对程序的影响。
本例文件参见光盘:..\ 第十章\循环删除前20行.xlsm
3.调整步长值提升循环效率
在For循环中,对于某些特殊的循环,可以利用修改步长值来提升循环的效率。
例如在工作表中列出今天所有星期日的日期。步长值为1的代码如下:
Sub 罗列今年周日日期A() '步长为1
Dim Item As Long, Star As Long, Ends As Long, i As Integer, tim As Long
tim = Timer
Star = DateSerial(Year(Date), 1, 1) + (7 - Weekday(DateSerial(Year(Date), 1, 1), 2) + 1) - 1
Ends = DateSerial(Year(Date) + 1, 1, 0)
For Item = Star To Ends
IF Weekday(Item, 2) = 7 Then i = i + 1: Cells(i, 2) = Format(Item, "yyyy-mm-dd")
Next Item
MsgBox "执行时间:" & Format(Timer - tim, "")
End Sub
该过程中步长值为1,计数器Item会累加300多次。
从单元格中的日期看,它每隔七天取出一个日期值。那么利用这个特点可以将步长设为7,那么IF表达式也可以忽略了,同时计数器的累加次数会减少到七分之一。代码如下:
Sub 罗列今年周日日期B() '步长为7
Dim Item As Long, Star As Long, Ends As Long, i As Byte, tim As Long
tim = Timer
Star = DateSerial(Year(Date), 1, 1) + (7 - Weekday(DateSerial(Year(Date), 1, 1), 2) + 1) - 1
Ends = DateSerial(Year(Date) + 1, 1, 0)
For Item = Star To Ends Step 10
i = i + 1
Cells(i, 1) = Format(Item, "yyyy-mm-dd")
Next Item
MsgBox "执行时间:" & Format(Timer - tim, "")
End Sub
这两个过程在效率上差异不大,但是后者一定高于彰者,因为它循环的次数更少。
过程中星期日的算法是:首先利用“DateSerial(Year(Date), 1, 1)”计算出今年1月1日的序列值,再用Weekday函数计算1月1日是星期几,根据这两个信息而得出第一个星期日的日期序列。然后再利用“DateSerial(Year(Date) + 1, 1, 0)”计算今年最后一天的日期序列,而For循环从Star开始循环直到Ends,每隔七天取出一个值并在单元格中逐个罗列出来即达成需求。
本例文件参见光盘:..\ 第十章\获取周日列表.xlsm
4.循环中执行多行语句
在循环结构中,可以执行多行语句,两行到200行不等,依需求而定。下面利用实例展示循环体中执行多语句。
公式为了解决发工资时的零钱问题,决定每月不放工资中的个位,而是将个位数转入下月;到了下月计算工资时,次本月的结余数(个位)累加到工资中,再取整发放,仍然结余个位到下下月……图中B列即为本月应发工资,C列是上月结余的个位数,现需计算本月每人的实发工资即取整后的值以及转入下月的个位数,并且将上月和本月结余皆为0者添加灰色背景。
图 工资表
根据以上信息,利用循环可以瞬间完成1000个员的工资计算。代码如下:
Sub 计算工资及零钱结余()
Dim i As Integer '声明变量,如果人数超过255需要改用Integer或者Long
= False '关闭屏幕更新
'从第二行开始循环,直到最后一行非空行
For i = 2 To Cells(, 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). = 6
Next i
= True '恢复屏幕更新
End Sub
该过程中,循环的范围是不确定,它的最小值是2,而最大值则工作表中非空行决定,行数多则循环次数多。
在For和Next之间,分别执行了三句代码,包括计算应发工资和结余,以及上月结余与本月结余均为0者加黄色背景。
本例文件参见光盘:..\ 第十章\计算工资中零钱结余.xlsm
5.根据需求中途退出循环
在个较大的范围中循环时,会消耗较多内存,而根据条件适时地退出循环则可以避免不必要的消耗。下面举一个实例说明如何在需要时退出循环。
Excel中有很多操作都会受限于合并单元格,例如排序、筛选、分列等等。在进行此类操作前,最后检查这个区域是否存在合并区单元格。本例即为开发一个自定义函数实现检查区域中是否包含合并单元格。Function代码如下:
Function MergeCell(rng As Range) '声明Function过程
'为避免用户选择的区域过大,例如多列或者整全工作表,限制区域是Rng与已用区域的交集
With (, rng)
Dim i As Long '声明变量
For i = 1 To .Count '遍历所有单元格
'如果某单元格的合并区域地址与它本身地址不本同
IF .Cells(i). <> .Cells(i).Address Then
Exit For '退出循环
End IF
Next i '检查下一个
'函数结果由计数器i与交集区域中单元格个数的比较结果来决定。其理论来源是:如果有合并元格,
'那么循环体执行次数一定少于单元格个数,在执行中途已经中断
MergeCell = i < .Count
End With
End Function
在以上过程中,需要说明三点:
(1)单元格逐个循环时对于整列或得全选工作表时会浪费很长时间,而用户实际的需求是只能对已用区域进行处理即可,所以可以将参数所代表的区域与已用区域计算交集,程序在交集中逐个检查是否有合并单元格即可。
(2)在函数的参数代表的区域存在一个或者多个合并单元格时,都结果都是一样——有合并单元格,那么程序设计时就可以在找到一个合并单元格后中止循环,不需再检查其它单元格,从而避免浪费不要的时间。中断循环的方式是插入语句“Exit For”。
(3)在步长为1的循环中,如果没有中途退出循环,那么计数器最后的值等于循环次数加1。即循环1000次后,计数器的值会成为1001。那么利用计数器的值可以得知循环的次数,而本例中正好需要利用该值来确定函数的结果。
其实VBA中有一个专用函数判断区或中是否存在合并单元格,本例仅仅演示循环语句的用法。内置的函数是MergeCells,如果区域内存在合并单元格则其属性值True,否则为False。
以图所示的数据为例,在单元格中录入公式后其结果如下(结果为True表示有合并单元格,结果为False时表示无合并单元格):
图 判断区域中是否存在合并单元格
本例文件参见光盘:..\ 第十章\判断区域中是否存在合并单元格.xlsm
Next循环的嵌套应用
循环也可以像条件语句一样多层嵌套使用,在每一层循环中也可以按需求随时中断循环。本例是双层循环的应用。
假设在工作簿的第一个工作表中有本期10个班三好学生名单,见图所示。需要将这些名单分置于10个工作表中,每个表以班名进行命名,且将学生姓名纵向存放。
实现以上需求可以使用双层循环完成。代码如下:
Sub 分班()
Dim 班 As Byte, 学生 As Byte '声明两个变量
'外层循环,遍历所有班级
For 班 = 2 To Cells(, 1).End(xlUp).Row
'添加一个新工作表,且位置在最后面
With (after:=Sheets())
'对新表命名
.Name = Sheets(1).Cells(班, 1)
'里层循环,遍历每个班的三好学生
For 学生 = 2 To Sheets(1).Cells(班, 1).End(xlToRight).Column
'将学生姓名复制到对应的班级中,且纵向存在A列
.Cells(学生 - 1, 1) = Sheets(1).Cells(班, 学生)
Next 学生 '执行下一个(学生)
End With
Next 班 '执行下一班(班级)
End Sub
在以上过程中,外层循环的范围是2到10,在A2:A10之间纵向循环。而里层循环的范围则由该班的人数则决定,在该表所在行中横向循环。
过程执行后的结果见图所示:
图 三好学生名单 图 将三好学生分存于10个工作表
本例文件参见光盘:..\ 第十章\分班.xlsm
利用循环获取工作表目录
建立目录有两种方法:工作表中建立表名配合链接函数完成以及数据有效性配合工作簿事件完成。
1. 工作表中建立表名配合链接函数创建目录
当工作簿中工作表太多时,想进入某个特定的表是一件很麻烦的事。例如每个工作表是一个产品的品名进行命名,现需查看其中某个产品的相关资料,Excel却没有提供查找工作表名的方法。本例利用循环创工作表目录,使用户可以通过查找的方式定位目标工作表。
本方法需要占用的单元格个数与视工作表个数而定。步骤如下:
(1)单击菜单【插入】\【模块】打开模块代码窗口;
(2)在模块代码窗口录入以下代码:
Sub 建立目录()
Dim i As Integer
= False '关闭屏幕更新,加快速度
For i = 1 To '遍历所有工作表
IF Sheets(i).Name = "工作表目录" Then '如果工作表名字等于“工作表目录”
GoTo Star '执行标签Star处的语句
End IF
Next
'如果变量i大于工作表数量(只有不存在“工作表目录”时才会i大于工作表数量)
'添加一个新工作表
= "工作表目录" '当前表名称等于“工作表目录”
Sheets("工作表目录").Move before:=Sheets(1) '“工作表目录”移到到前面
Star:
Range("A:B").Clear '清除AB两列的值
Cells(1, 1).Value = "编号" '在指定列显示编号
Cells(1, 2).Value = "目录" '编号右边一列显示目录
For i = 2 To '遍历目录以外的所有工作表
Cells(i, 1).Value = i - 1 '在编号列记录编号
'添加超级链接,实现单元格单击时进入相应的工作表
Anchor:=Cells(i, 2), Address:="", SubAddress:="'" & Sheets(i).Name & "'!A1", TextToDisplay:=Sheets(i).Name, ScreenTip:="单击打开:" & Sheets(i).Name
Next
Cells(1, 1).Resize(, 2). = xlLeft '左对齐
= True '恢复屏幕更新
End Sub
在该过程中,首先对当前工作簿中所有工作表循环一次,同时检查每个工作表名是否等于“工作表目录”,如果有则跳到至标签Star后的语句,否则新建一个工作表,命名为“工作表目录”。
然后再次循环工作表,并在“工作表目录”中建立超链接,每个链接关联至对应的工作表,同时对目录进行编号。
(3)光标定位于代码中任意位置,并按下快捷键【F5】执行过程。执行后的效果见图所示。当光标指向任意链接时会弹出提示,若单击即可进入该工作表。更重要的是工作表名称建立在单元格中,使用换找方式定位目标。
图 工作表目录
本例文件参见光盘:..\ 第十章\创建工作表目录.xlsm
2. 数据有效性配合工作簿事件创建目录
本方式的优势在于只需要一个单元格,所以可以建立在任意工作表中,不限“工作表目录”;缺点是数据有性效下拉列表无法使用查找方式定位。
本方法创建目录之步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中输入以下代码:
Public sht As Range '声明一个公共变量
Sub 目录()
Dim i As Integer
'让用户自由指定目录存放的地址,包括工作表与单元格地址
Set sht = ("请选择存放目录的单元格,可以选择其它工作表", "目标单元格", "A1", , , , , 8)
= False '关闭屏幕更新
For i = 1 To '遍历有工作表
(i, ) = Sheets(i).Name '所有工作表名存入指定表的最后一列
Next
With '添加数据有效性
.Delete '删除已有有效性设置(假设有的话)
'添加序列,其地址为刚才所创建的辅助区
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:="=" & Cells(1, ).Resize(, 1).Address
.InCellDropdown = True '显示含有有效取值的下拉列表
= 4 '添加背景色突出显示
End With
= True '恢复屏幕更新
End Sub
在该过程中,首先声明一个公共变量,因为在模块中与工作簿事件都需要调用它。然后在程序执行时弹出对话框,让用户选择一个单元格用于存放目录,该单元格可以是任意工作表中的单元格。程序会将所有工作表名字罗列在该单元格所在工作表的最后一列,做为目标单元格的数据有效性引用源,即建立目录的单元格从辅助区中引用工作表名。
(3)如果未显示工程资源管理器则使用快捷键【Ctrl+G】调出,然后双击“ThisWorkbook”进入工作簿事件代码窗口;
(4)在工作簿事件代码窗口中输入以下代码:
'声明工作簿事件
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
'防错
On Error Resume Next
'如果活动工作表的名字与建立工作表目录的工作表名字相同
IF = Then
'如果选区只有一个单元格而且单元格中非空白
IF = 1 And Len(Target(1)) > 0 Then
'如果单元格地址等于存放目录的单元格地址那么激活与当前活动单元格中字符相同的工作表
IF = Then Sheets().Activate
End IF
End IF
End Sub
该事件过程中需要防错,因为有可能工作表删除后没有更新目录而导致单击目录时跳转失败。
然后利用IF进行了五个条件的限制,完全符合条件则执行语句:跳转到选择的工作表。条件包括:当前工作表与公共变量Sht所在工作表名相同、当前选区仅仅一个单元格、单元格非空、活动单元格地址等于公共变量Sht的地址。
(5)录入完所有代码后,返回工作表中,使用快捷键【Alt+F8】打开“宏”对话框,从中选择“目录”并执行;
(6)执行程序时弹出的“目标单元格”默认指当前工作表A1单元格,可以任意选择工作表及单元格。见图所示:
(7)假设用户选择的单元格时工作表“周二”中的B2,那么程序执行完毕后,进入“周二”工作表,单击B1单元格将弹出下拉列表,列表中有当前工作表所有工作表名,见图所示。
图 选择存放目录的单元格 图 单元格下拉列表式目录
不管从列表中选择任何表名,程序会自动跳转到工作表中,这是因为改变B2单元格的值时触发了工作簿事件
(8)再次执行过程“目录”,将目标单元格选择“周四”中的G20,那么此时单击“周四”工作表中的G20单元格才可以触发工作簿事件,从而实现工作表目录的跳转,这是共公变量“Sht”已产生变量致。虽然“周二”工作表B2单元格仍然保留了下拉列表,但不再有跳转功能。
本例文件参见光盘:..\ 第十章\利用数据有性设建立工作表目录.xlsm
以上两个过程演了For循环在获取工作表名中的作用,当然它还有其它很多用法,在后续章节中还会有大量的案例。
For 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语法详解
部分
描述
element
必要参数。用来遍历集合或数组中所有元素的变量。对于集合来说,element 可能是一个 Variant 变量、一个通用对象变量或任何特殊对象变量。对于数组而言,element只能是一个 Variant 变量。
group
必要参数。对象集合或数组的名称(用户定义类型的数组除外)
statements
可选参数。针对 group 中的每一项执行的一条或多条语句
Exit For
可选参数。表示退出循环
在For next循环中可以自由设定循环的范围,而For Each Next循环则无法设定范围,而是由对象的数量来决定。例如在Sheets集合中循环,那范围就是所有工作表,相当于1到工作表总数之间。
参数element必须是对象,而Group代表该对象的集合。例如在Sheets对象集合中循环时,那么element就需要申明为工作表对象;如果在图形对象集合Shapes中循环时,那么element那要申明为图形对象Shape……
例如图中建立工作表目录的需求,将For Next改为For Each Next语句也可以完成。代码如下:
Sub 建立目录() 'For Each Next法
Dim sht As Worksheet, i As Integer
= False '关闭屏幕更新,加快速度
For Each sht In Sheets '遍历所有工作表
IF = "工作表目录" Then '如果工作表名字等于“工作表目录”
GoTo Star '执行标签Star处的语句
End IF
Next
'如果变量i大于工作表数量(只有不存在“工作表目录”时才会i大于工作表数量)
'添加一个新工作表
= "工作表目录" '当前表名称等于“工作表目录”
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 <> "工作表目录" Then '如果对象变量Sht的名字不等于“工作表目录”
'在编号列记录编号,编号的依据是A列的非空单元格个数
Cells(([a:a]) + 1, 1) = ([a:a])
'添加超级链接,实现单元格单击时进入相应的工作表
Anchor:=Cells(([a:a]), 2), Address:="", SubAddress:="'" & & "'!A1", TextToDisplay:=, ScreenTip:="单击打开:" &
End IF
Next
Cells(1, 1).Resize(, 2). = xlLeft '左对齐
= True '恢复屏幕更新
End Sub
在本过程中,利用对象变量Sht来遍历所有工作表,然后获取工作表目录。与For Next循环不同的是它不能随意限制范围,例如从第二个工作表开始,所以代码中需要利用IF来排除“工作表目录”。
利用循环选择区域中所有负数
Excel的查找和定位功能都无法完成选择区域中所有负数,这不得不说是一个遗憾。所幸VBA可以轻松地达成这类需求。
本例利用循环选择当前工作表中所有负数,展示For Each Next语句的应用技巧。具体步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中录入以下代码:
Sub 选择进出库记录中的负数()
Dim rng As Range, rngg As Range '声明两个对象变量
'将B列和E列合并为一个区域,然后在该区域与已用区域的交集中循环
For Each rng In Intersect(, Union([b:b], [e:e]))
'如果rng对象为负数
IF rng < 0 Then
'如果rngg变量尚未初始化
IF rngg Is Nothing Then
Set rngg = rng '将查到的负数单元格赋与变量rngg
Else '否则
Set rngg = Union(rng, rngg) '合并变量rng与rngg
End IF
End IF
Next rng
'选择所有负数单元格
End Sub
在此过程中,需地注意的事项有三点:
(1)本例中要求是选择B列和E列的负数,虽然本例中其它列不存在负数,直接在中进行查找也可以完成相同的任务,但是只在B列和G列数据区进行循环可以节约时间、提升工作效率;
(2)如果将查找的目标区域写成“Range("b3:b12,e3:e12")”也可以达成同等效果,但编写程序一定要注意通用性,当数据增减时也可以使代码适应数据变化;
(3)当查找到一个小于0的单元格时,将它赋与变量Rngg,直到找出所有目标单元格。但是在第一次找到小于0的值时,变量Rngg尚未始化,不能将它与变量Rng进行合并。需要利用IF判断后,直接将变量rng赋与Rngg,而以后查找到符合条件的目标时才可以将Rng与Rngg进行合并。
执行程序后,结果见图所示(为了便于查看,将选区添加红色背景):
图 仓库进出表 图 选择负数
本例文件参见光盘:..\ 第十章\选择负数.xlsm
利用循环统一所有图片高度及对齐单元格
在工作表中批量插入图形对象时,其高度与宽度与图形原的尺寸制约,没有提供用于指定统一尺寸的工具,也无法用简单的方式对齐单元格。而利用VBA的循环可以瞬间完成其尺寸调整及对齐单元格,具代步骤如下:
(1)假设工作表中有图所示数据与图片。使用快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【模块】进入模块代码窗口;
(3)在窗口中输入以下代码:
Sub 统一高度()
Dim shap As Shape '声明图形对象变量
'遍历所有图形对象
For Each shap In
= 80 '统一高度为80
'将每个图片对齐它左下角的单元格左边线
=
'将每个图片对齐它左下角的单元格上边线
=
Next shap
End Sub
(4)光标位于过程间任意位置,并按下快捷键【F5】执行程序,工作表中所有图形对象在瞬间完成调整。调整后效果见图所示:
图 混乱的图形对象 图 利用循环对齐图片及统一高度
在该教过程中,利用图形对象变量在Shapes对象集合中循环,逐一调整各对象的高度及左边距与上边距。而图片的宽度则可以忽略,因为默认情况下,图形对象在调整大小时是锁定纵横比的,高度变化时宽度与会自动相应地变化。
另外值得一提的是Shapes对象集合中包含了自选图形、任意多边形、OLE 对象、艺术字、文本框和图片对象等等。如果需要仅仅对图片进行调整,而忽略其它所有对象,那么需要改用以下代码:
Sub 统一高度()
Dim shap As Shape '声明图形对象变量
'遍历所有图形对象
For Each shap In
'如果是图片
IF = 13 Then
= 80 '统一高度为80
'将每个图片对齐它左下角的单元格左边线
=
'将每个图片对齐它左下角的单元格上边线
=
End IF
Next shap
End Sub
重点在于对象的Type属性,如果它等于13则表示对象是图片。在过程中通过IF将非图片的Shape对象排除即可达成需求。
Shape的可用属性见表10-12所示:
表10-12 Shape对象的可用属性列表
名称
值
描述
msoShapeTypeMixed
-2
混和形状类型
msoAutoShape
1
自选图形
msoCallout
2
标注
msoChart
3
图
msoComment
4
批注
msoFreeform
5
任意多边形
msoGroup
6
组合
msoEmbeddedOLEObject
7
嵌入的 OLE 对象
msoFormControl
8
窗体控件
msoLine
9
线条
msoLinkedOLEObject
10
链接 OLE 对象
msoLinkedPicture
11
链接图片
msoOLEControlObject
12
OLE 控件对象
msoPicture
13
图片
msoPlaceholder
14
占位符
msoTextEffect
15
文本效果
msoMedia
16
媒体
msoTextBox
17
文本框
msoScriptAnchor
18
脚本定位标记
msoTable
19
表
msoCanvas
20
画布
msoDiagram
21
图表
msoInk
22
墨迹
msoInkComment
23
墨迹批注
msoIgxGraphic
24
IGX 图形
本例文件参见光盘:..\ 第十章\统一图片高度及对距.xlsm
Do Loop语法详解
Do Loop是一种循环语句,表示当条件为 True 时,或直到条件变为 True 时,重复执行一个语句块中的命令。
可以使用 Do Loop 语句去运行一句或者一组语句,而它所用掉的时间是则指定的条件来决定。当条件为True 或直到条件变成True 时,此语句会一直重复,如果设计的条件一直都符合,那么它会永远循环下去,直到用户手工中断程序或者关闭Excel程序。
Do Loop 语句有两种表达形式,下面分别进行讲解。
语法一:
Do [{While | Until} condition]
[statements]
[Exit Do]
[statements]
Loop
Do Loop循环各部分的含义如下:
表10-13 Do Loop语句各部分含义详解
部分
描述
condition
可选参数。数值表达式或字符串表达式,其值为 True 或 False。如果 condition 是 Null,则 condition 会被当作 False
statements
一条或多条命令,它们将被重复当或直到 condition 为 True
Exit Do
用于中途退出循环,可选项
其中While与Until表示可以选择其中之一做为参数,它们的区别是While表示只如果条件为True则继续循环执行,而Until表示循环执行,直到条件这True。很多时候它们可以互替代,在部分条件下却会有区别。
下面通过一个小小的循环比较While与Until在Do Loop中的差异。
假设某产品从上线生产到产量正常需要一个适应期,而该产品的日产量标准在1000到1200之间。现在图中统计了该产品的每日产量,求产品上线到产量正常经过了多少天。
利用While与Until分别提供两种解,其代码如下:
Sub 第几日产量正常A()
Dim i As Integer '声明变量
i = 2 '初始化变量,从第二行开始循环
Do While Cells(i, 2) < 1000 '只要小于1000就继续执行
i = i + 1 '累加计数器
Loop
MsgBox i - 1 '报告计数器的值
End Sub
Sub 第几日产量正常B()
Dim i As Integer '声明变量
i = 2 '初始化变量,从第二行开始循环
Do Until Cells(i, 2) >= 1000 '循环执行,直到大于等于1000
i = i + 1 '累加计数器
Loop
MsgBox i - 1 '报告计数器的值
End Sub
第一个过程使用While,表示条件为True则继续执行,那么它的条件必是小于1000,那么遇到第一个大于或者等于1000的值后,条件变成False,将中断循环;而第二个过程使用Until,表示循环执行,直到条件变为True,那么它的条件则需要设置为大于等于1000,当到它第一个大于等于1000的值时,条件成立,那么中断循环。
图 产量表 图 达到正常产量的天数
语法二:
Do
[statements]
[Exit Do]
[statements]
Loop [{While | Until} condition]
和语法一不同的是While与Until参数置于循环体的结尾。置于条件之前表示先检查条件表达式,然后执行循环体;而置于末尾则表示循环至少一次后才检查条件表达式,这在特殊条件下会直接影响到计数器的值。
仍然以前面的实案例数据进行演示对比,两段代码如下:
Sub 第几日产量正常A()
Dim i As Integer '声明变量
i = 1 '初始化变量,从第一行开始循环
Do
i = i + 1 '累加计数器(先累加再检查条件)
Loop While Cells(i, 2) < 1000 '只要小于1000就循环执行
MsgBox i - 1 '报告计数器的值
End Sub
Sub 第几日产量正常B()
Dim i As Integer '声明变量
i = 1 '初始化变量,从第二行开始循环
Do
i = i + 1 '累加计数器(先累加再检查条件)
Loop Until Cells(i, 2) >= 1000 '循环执行,直到大于等于1000
MsgBox i - 1 '报告计数器的值
End Sub
鉴于While与Until参数置于循环体的结尾时的特点是“先累加计数器后检查条件”,那么初计数器初始化的值必须小于待计算的第一个单元格行号,否则当第天就达到标准备,会产生计算错误。
在工作表中循环获取所有字体
Do Loop在工作中的实际应用时执行效率极高,本例利用Do Loop循环来产生Excel支持的所有字体。实现步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中录入以下代码(分别利用Do Loop的四种方式产生四段代码):
Sub GetFontList1()
On Error Resume Next
Dim Fonts As CommandBarComboBox, i As Integer
Set Fonts = (ID:=1728) '字体下拉列表
Do While Err = 0 '只要不出现错误就一直循环执行
Cells(i + 1, 2) = (i + 1) '在单元格罗列出字体
i = i + 1 '累加计数器
Loop
End Sub
Sub GetFontList2()
On Error Resume Next
Dim Fonts As CommandBarComboBox, i As Integer
Set Fonts = (ID:=1728) '字体下拉列表
Do
Cells(i + 1, 1) = (i + 1) '在单元格罗列出字体
i = i + 1 '累加计数器
Loop While Err = 0 '只要不出现错误就一直循环执行
End Sub
Sub GetFontList3()
On Error Resume Next
Dim Fonts As CommandBarComboBox, i As Integer
Set Fonts = (ID:=1728) '字体下拉列表
Do Until Err <> 0 '循环执行,直到出现错误时停止
Cells(i + 1, 3) = (i + 1) '在单元格罗列出字体
i = i + 1 '累加计数器
Loop
End Sub
Sub GetFontList4()
On Error Resume Next
Dim Fonts As CommandBarComboBox, i As Integer
Set Fonts = (ID:=1728) '字体下拉列表
Do
Cells(i + 1, 4) = (i + 1) '在单元格罗列出字体
i = i + 1 '累加计数器
Loop Until Err <> 0 '循环执行,直到出现错误时停止
End Sub
以上四段代码分别使用While和Until置于循环体前与后来获取字体,它们在写法稍有差异,但都可以产生同样结果。
(3)选择第一个过程,并按下快捷键【F5】执行过程,在A列将罗列出所有字体;
(4)继续执行过程二、三、四,可以发现,它的效果完成一致,见图所示:
图 四种方法获取字体列表
当然,除了Do Loop循环以外,用其它的方式仍然可以完成同等效果,例如使用For Next,代码如下:
Sub GetFontList5() 'For Next法
Dim Fonts As CommandBarComboBox, i As Integer
Set Fonts = ("Formatting").FindControl(ID:=1728)
'遍历字体下拉列表
For i = 1 To
Cells(i, 1) = (i) '在单元格中逐个列出字体
Next
End Sub
本例文件参见光盘:..\ 第十章\罗列字体.xlsm
计算得分累加到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 '累加计数器,初始化为1
sums = (Range("B2").Resize(i, 1))
Loop
'报告结果
MsgBox "第" & i & "场达到1000分", 64, "提示"
End Sub
Sub 得分累积到1000的场次B() 'For Next法
Dim sums As Integer, i As Integer '声明变量
For i = 1 To 1000 '遍历所有场次
'逐场累加得分
sums = (Range("B2").Resize(i, 1))
IF sums >= 1000 Then '如果大于等于1000
'报告结果
MsgBox "第" & i & "场达到1000分", 64, "提示"
Exit For '退出循环
End IF
Next
End Sub
Sub 得分累积到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 + rng
IF sums >= 1000 Then '如果大于等于1000
'报告结果
MsgBox "第" & i & "场达到1000分", 64, "提示"
Exit For '退出循环
End IF
Next
End Sub
在以上四个过程中,使用了三种不同地计算方式,在执行效率上差异极小。
(3)将光标定位于过程并使用快捷键【F5】执行程序,三种过程可以得到相同的结果,见图所示:
图 得分表统计 图 统计积分
本例文件参见光盘:..\ 第十章\计算得分累积到1000的场次.xlsm
利用循环产生文字动画
Flash或者网页中可以实现滚动文字,有一种炫目的感觉。在VBA中利用循环也可以实现,而且可以随意控制速度。具体步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中录入以下代码:
Dim 停 As Byte '声明一个公共变量
Sub 字符滚动()
Dim j As Integer
停 = 1 '将变量赋值为1
[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 Sub
Sub 停止滚动() '将变量赋值为0,从而停止动画
停 = 0
End Sub
(3)返回工作表,单击功能区【开发工具】\【插入】\【按钮】(表单控件中的按钮);
(4)在工作表中按住鼠标左键向右下拖动,从而绘出一个按钮;
(5)此时Excel会弹出弹出一个“宏”对话框,见图所示。在宏名列表中选择“字符滚动”;
(6)同样方式添加第二个按钮,将其宏名设置为“停止滚动”;
(7)将两个按钮的显示字符分修改为“开始”和“停止”;
(8)单击“开始”按钮,单元格A1中的字符即开始从右向左滚动,见图;
(9)随时单击“停止”按钮可以中止动画。
图 为按钮指定宏 图 用按钮控件字符串滚动
在本例代码中需要注意三点:
(1)利用两个过程控制变量值的,变量必须声明为公共变量;
(2)在VBA的循环中,VBA会占用较多的内存,而且在循环过程中它一直占用控制权,其它程序可能会暂停。类似于Msgbox对话框显示状态下,过程中任何程序都处理暂停一样。而在实现动画效果的循环中也一样无法单击其它按钮来停止动画,因为其它按钮此时无法获取控制权。为了让用户单击“停止”时可以执行过程“停止滚动”,需要使用 DoEvents方法让循环暂时将控制权移动其它程序,系统处理其它的事件。同时移交控制权将会让动画效果突显出来,否由用户可能无法看到动画;
(3)本例方式实现动画的速度与电脑硬件配置有关,所以不同电脑中执行该过程的动画效果可能大大不同。用户需要根据自己的实际情况修改For循环中的范围,例如将1800修改为1000或者2500等等,看看它在速度上有何变化。
本例文件参见光盘:..\ 第十章\单元格文字动画.xlsm
With语句
With语句在前面的章节中已经多次提到,本节对With语句的用途、语法及常见错误进行分析。
With语句的用途与语法
With语可以在一个单一对象或一个用户定义类型上执行一系列的语句。它的主要作用是简化代码、提升执行速度及减少变量的使用。
1.简化代码
例如某个对象在一个过程需要多次调用,那么With语句可以使编写代码时只写一次,却可以达成多次效用的效果。这对于代码的简化有着举足轻重的作用。
例如对单元格添加数据有效性,不用With语的代码如下:
Sub 对B1设置数据有效性1()
Worksheets("生产表").[B1].
Worksheets("生产表").[B1]. Type:=xlValidateList, Formula1:="=$A$1:$A$8"
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = "提示"
Worksheets("生产表").[B1]. = "数据来源是A1:A8"
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = True
End 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 = True
End With
End Sub
很显得With在简化代码中的有着可能小觑的贡献
2.提升速度
仍然以上面的两段代码稍加修改,进行比较,可以证实With句对循环语句可以大大的提速。
Sub 对B1设置数据有效性1()
Dim tim As Long
tim = Timer
For i = 1 To 1000
Worksheets("生产表").[B1].
Worksheets("生产表").[B1]. Type:=xlValidateList, Formula1:="=$A$1:$A$8"
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = "提示"
Worksheets("生产表").[B1]. = "数据来源是A1:A8"
Worksheets("生产表").[B1]. = True
Worksheets("生产表").[B1]. = True
Next i
MsgBox Format(Timer - tim, "") & "秒"
End Sub
Sub 对B1设置数据有效性2()
Dim tim As Long
tim = Timer
For i = 1 To 1000
With Worksheets("生产表").[B1].Validation
.Delete
.Add Type:=xlValidateList, Formula1:="=$A$1:$A$8"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = "提示"
.InputMessage = "数据来源是A1:A8"
.ShowInput = True
.ShowError = True
End With
Next i
MsgBox Format(Timer - tim, "") & "秒"
End Sub
两段都是对B1单元格添加有效性设置,为了更容易看出区别,将程序循环执行1000次。分别执行以上两段代码,它们最后所报告的执行时间会差异两倍左右。
本例文件参见光盘:..\ 第十章\利用With提速.xlsm
3.减少变量
有一种特殊的对象,无法多次引用。如果第二次调用它,那么将产生不同的对象,而不是第一次引用的对象。这主要是新建一个对象时会涉及到。
通常解决这个问题是采用变量来取代它,那么以后调用这个变量即可。但程序中变量太多会对程序的执行效率产生副作用。那么剩下的方案就是使用With语句了。
例如在工作簿中建立一个新工作表,并进行命名、移动处理。使用变量的处理方式是:
Sub 建立总表且移至最后()
Dim sht As Worksheet '声明一个工作表对象
Set sht = '将新表赋与变量
= "总表" '设置新表的名称
after:=Sheets() '移动新表
End Sub
对于“”这类新建对象,是不能多次引用的,如果直接对对象赋值那么每个赋值都会产生一个新的对象。例如:
Sub 建立总表且移至最后2()
= "总表" '设置新表的名称
after:=Sheets() '移动新表
End Sub
该过程中两次“”代表了两个对象,会产生两个新工作表。
如果改用With语句则可以完美处理以上所提到的两个问题。With语句如下:
Sub 建立总表且移至最后3()
With '创建一个工作表对象,后续可以多次调用这个对象
.Name = "总表" '设置With对象的名称
.Move after:=Sheets() '称动With对象到最后
End With
End Sub
该过程中With语句已经生成了一个新的对象,而后续的代码中可以随意调用这个对象,不再需要变量,也不会产生多个新工作表。
4.语法解析
通过前面的分析,With语句对程序简化与提速都很重要,那么现在对其语法进行详细地分析,便于读者理解并应用到工作表中去。
With语句的语法如下:
With Object
[statements]
End With
其中object代表一个对象,而且必须是对象。例如以下语句中:
Sub test1()
With [a1]
MsgBox .Value
End With
End Sub
以上过程可以获取单元格对象A1的值。但是对With的对象改用表达式则不符合规则。例如:
Sub test2()
With [a1] + [a2]
MsgBox .Value
End With
End Sub
在上过程中,“[a1] + [a2]”是一个可计算的表达式,不是对一个对象。所以执行程序时会弹出运行时错误。
而statements是可选参数,表示要执行在 object 上的一条或多条语句。通过它包括了object对象中的某个属性。而且虽然必须要包括某个属性,否则代码就失去了With的意义。
每个对象都有一个默认属性,在引用时可以忽略不写。但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 With
With .Interior '里面With,对象是单元格的内部属性
.Pattern = xlSolid ''里层With的执行语句
.PatternColorIndex = xlAutomatic
.Color = 65535
.TintAndShade = 0
'.Name = "宋体" '执行此句也会出错,因为字体名称属性在前一个With语句中才可以调用,
'如果此处一定要调用,那么需要指明其上层对象,例如改用:. = "宋体"
End With
End With
End Sub
读者可以仔细比较三个With语句的用法。
With语句实例
With语句对于条件语句和循环语句在使用上都更简单,但也有必要通过几个实例来加深读者对With语句的理解程度。
1.批量生成复选框
在工作表中设计一个复选框(方框中打勾的控件)并调整其大小与边距适应单元格虽然不难,但绝对是一个较费时的事。而如果批量地产生复选框,且所有复选框对齐并统一大小就成为一个难题了。但VBA处理这个问题却轻松自如,实现步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中录入以下代码:
Sub 批量生成复选框()
Dim rng As Range '声明一个对象变量
= False '并闭屏幕更新,加快速度
'遍历选区每一个单元格
For Each rng In Selection
'外层With,简化变量Rng的调用
With rng
'里层With,对象是插入的复选框
With (.Left, .Top, .Width, .Height)
.Text = "合格" '设置复选框的显示文字
.Value = True '呈选中状态
End With
End With
Next rng '执行下一个
= True '恢复屏幕更新
End Sub
(3)返回工作表中,选择待添加复选框的单元格,例如图中,按住Ctrl键选择B2:B2和D2:D5两个区域;
(4)使用快捷键【Alt+F8】打开“宏”对话框,然后执行程序“批量生成复选框”。程序执行结果见图所示,每个单元格中都出现一个名为“合格”的复选框,且全部都与单元格对齐。
图 在工作表中批量生成复选框
在本例中,使用了两层With嵌套。其中外层的With可用可不用,里层的With却必须使用,如果利用两个“”来替换With语句,那么此时的两句代码将代表两个对象,而不是同一个。
另外,对于任何对象在新建时都会占用一些内存,而且屏幕一直在刷新、闪动,为了提升提序整体效率,必须使用ScreenUpdating属性来关闭刷新。
本例文件参见光盘:..\ 第十章\批量生成复选框.xlsm
2.让图形对象设计动画
在工作表可以插入图形对象,如果能使用图形对象无休止的滚动岂非眩目?VBA完全可以胜任。实现步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口中输入以下代码:
Sub 旋转图形()
Dim i As Integer
With
For i = 1 To 10000 '循环时次数,可以控件旋转时间
IF i Mod 100 > 50 Then
'变化大小
.(1) = (i Mod 50) / 100 '控制图形的大小
.IncrementRotation 1 '旋转一个单位
DoEvents
. = (i Mod 50) '修改填充色,不断变化
DoEvents
Else
.(1) = - (i Mod 50) / 100 '控制图形的大小
.IncrementRotation 1 '旋转一个单位
DoEvents
. = (i Mod 50) '修改填充色,不断变化
DoEvents
End IF
Next
End With
End Sub
(3)返回工作表,单击功能区【插入】\【形状】\【太阳形】(符号:),然后在工作表中给制一个太阳形状,见图所示:
(4)选择绘制的图形,按下快捷键【Alt+F8】打开“宏”对话框,选择“旋转图形”并执行,图形对象会自动开始向顺时针旋转。而且旋转过程中,它会不断的变化颜色及放大、缩小,图是旋转过程中的一个截图。
图 插入太阳图形 图 旋转过程中的截图
在该过程中,反复调用“”多次,利用With语句可以大大地简化代码,同时提升代码的编写效率与执行效率。
本例文件参见光盘:..\ 第十章\旋转图形.xlsm
With语句常见错误分析
With语句在使用上极简单,但初学者可能仍然会出现诸多问题,笔者也在初期经历了多次的出错后才积累了一些应用经验。
With语句在工作中常见的问题主要表现在三个方面:
1.忘记写“End With”
当With与End With之间有很多行代码时,常常会忘记写End With,而造成图所示错误。
当然,这是未养成好的代码录入习惯所造成的,解决这个问题需要严格按照本书第九章中关于“配对语句的录入方式”所讲的方式进行。
With放错了位置
当过程中除With语句外,还包括了条件语句或者循环语句等需要配对的语句时,有可能出现End With放错了位置的情况。
例如在以下过程中,With语句与IF语句共用,而End IF和End With两句的位置刚好错位,所以执行时会产生图所示编译错误。
Sub test()
With Range("A1")
IF .Value = 1 Then
Range("B2").
.Value = .Value + 1
End With
End IF
End Sub
图 忘记写End With之错误 图 End With错位之错误
3.里层With代码调用外层With对象的属性
当过程中有多个With嵌套时,引用对象是需要分层级的。如果需要调用外层的对象,必须完整地录入整个对象,而不能前置小圆点去引用。
例如以下过程中,在里层的With语句中调用单元格的Left属性时,不能使用前置小圆点,而需要完整的注明对象。代码中“MsgBox .Left”需要改为“MsgBox Range("A1").Left”。
Sub Test()
With Range("A1")
With .Interior
MsgBox .Left
End With
End With
End Sub
同理,如果在外层需要调用里层的对象时,也需要完整地注明里层对象。
错误处理语句
在开发程序时,一定会出现执行错误,包括可以预料的错误、意外的错误,以及开发者故意设置的错误等等。在过程中有必要对这些错误进行处理,防止程序中断或者得到错误的运算结果。
还有一种情况是重写VBA的错误提示,因为部分情况下VBA的提示可能并非实际的错误原因,用户无法从中获得有用的资讯,开发者有必要重写一个错误提示,使终端用户从中获取出错原因或者清楚正确的操作方式。
本节对程序出错的原因、含义、错误捕捉设置、以及防错的方法进行一一解说。
错误类型与原因
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 Byte
ans = ("输入1:加密" & Chr(10) & "输入2:解密", , , , , , , 1)
更多代码
End Sub
在以上过程中,因为正常情况下变量ans只需要1到2这个范围之间变化,那么编程时将其数据类型声明为Byte。而用户可能会故意胡乱的输入300以上的或者0以下的数据,那么程序就会中断,产生“溢出”错误。
基于以上的各种原因,程序在使用过程中一定会出现错误。那么在代码中进行防错,就显得极为重要。
Err对象及其属性、方法
在VBA中提供了一个Err对象,用于提示关于运行时错误的信息。
Err 对象的属性由错误的生成者来设置,包括VBA程序,代码中涉及的对象或者是程序开发者。Err对象的属性和方法可以通过VBA的“自动列出成员”来获取,方法是在代码窗口中输入“Err.”,VBA会自动弹出一个成员下拉列表,其中带有绿色图标者表示方法,其余的表示属性见图所示。
图 Err对象的成员列表
Err的属性参见表10-14,Err对方法参见表10-15.
表10-14 Err属性详解
属性
含义
Number
返回或设置表示错误的数值。Number 是 Err 对象的缺省属性。可读/可写
HelpContext
返回或设置一个字符串表达式,包含 Windows 帮助文件中的主题的上下文 ID。可读 / 可写
HelpFile
返回或设置一个字符串表达式,表示帮助文件的完整限定路径。可读/可写
Source
返回或设置一个字符串表达式,指明最初生成错误的对象或应用程序的名称。可读写
LastDLLError
返回因调用动态链接库 (DLL) 而产生的系统错误号。只读。 在Macintosh中,LastDLLError 总是返回零
Descriptio
返回或设置一个字符串表达式,包含与对象相关联的描述性字符串。可读/可写
表10-15 Err方法详解
属性
含义
Raise
产生运行时错误
Clear
清除 Err 对象的所有属性设置
Err的属性中使用最频繁的是其Number属性,而方法中使用频繁的是Clear。在本书前面的章节和后面的章节都有关于Number属性和Clear方法的应用。
认识Error函数
Error函数返回对应于已知错误号的错误信息。
VBA内部对VBA的所有错误都有相应的编码,利用该编号可以模拟一个错误信息。Error函数语法如下:
Error [(errornumber)]
其参数代表错误编码,例如编码6表示“溢出”错误。完整代码如下:
Sub 溢出错误()
Error 6
End Sub
执行以上过程时会弹出图所示错误提示框:
图 模拟溢出错误
Error函数和 的功能类似,都可以人工设计一个错误。
罗列错误代码及含义
VBA中有近100个错误类型,当程序出错时会弹出对个对话框,而该对话框中简短的几字符往往无法让用户明白出错的实际原因。
本例利用了Err的多个属性在单元格中产生错误错误、描述及错误含义详查。具体实现步骤如下:
(1)单击【插入】\【模块】打开模块代码对话框;
(2)在模块代码窗口中输入以下过程代码:
Sub 获取所有错误类型编码及含义()
Dim i As Integer, num As Integer
Range("A1:C1") = Array("错误ID", "错误描述", "帮助主题") '一次性写三个单元格的标题
For i = 1 To 1000 '在获取编号为1到1000的错误中循环
On Error Resume Next '遇到错误仍然继续执行
i '人工产生一个错误,其编号随循环而递增
'如果错误类型不是"应用程序定义或对象定义错误"或者编号等于1就提取该错误描述
IF Error(i) <> "应用程序定义或对象定义错误" Or i = 1 Then
'利用变量num获取A列第一个非空单元格的行号
num = (Range("a:a")) + 1
Cells(num, 1) = i '将错误编号赋与A列第一个非空单元格
Cells(num, 2) = Error(i) '将错误描述赋与B列第一个非空单元格
Cells(num, 3) = '将错误描述赋与C列第一个非空单元格
End IF
Next i
End Sub
在以上过程中,首先使用了“On Error Resume Next”防错,表示不管遇到什么错误都继续执行下去;然后通过Raise方法人为产生一个错误,从而可以捕捉该错误的帮助主题;最后在是A、B、C三列中罗列出错误编号、认描述和帮助主题。
(3)为了让单击工作表中的错误类型可以弹出详细的帮助主题,还需要编写工作表单击事件以调用对应的帮助主题。双击工程资源管理器中的“Sheet1”,然后输入以下单击事件代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'只在单击第二列时和效
IF = 2 Then
Dim str As String
On Error Resume Next '防错
1 '故意生成一个错误,用于提取本电脑中帮助文件的文件夹
str = '获取帮助文件文件名
'利用Help方法打开对应的帮助文件,即错误类的详细描述
CStr(), Cells(, 3)
End IF
End Sub
以上过程利用IF限制单击事件在只有B列才能触发;然后人为产生一个错误,并捕捉到错误的帮助文件名字;最后利用Help方法打开对应的帮助主题。
(4)返回工作表界面,使用快捷键【Alt+F8】打开“宏”对话框,选择“获取所有错误类型编码及含义”,执行过程后将在工作表中产生图所示错误类型描述。
(5)单击B6单元格“溢出”,将分打开与“溢出”相关的帮助主题,见图所示。
图 错误类型认识描述及其帮助主题 图 溢出错误的帮助信息
此工作表可以作为日常工作中的VBA错误速查表。而且过程中涉及了Err相关的多种属性与方法,读者可以从中了解Err相关语法的应用。
本例文件参见光盘:..\ 第十章\ VBA内置错误类型速查表.xlsm
VBA的错误时处理机制
VBA中有专用的防错语句:On Error语句。它可以启动一个错误处理程序并指定该子程序在一个过程中的位置;也可用来禁止一个错误处理程序。
On Error语句主要包括三类,见表10-16所示:
表10-16 VBA的错误处理语句
语句
用途
On Error GoTo line
启动错误处理程序,且该例程从必要的 line 参数中指定的 line 开始。line 参数可以是任何行标签或行号。如果发生一个运行时错误,则控件会跳到 line,激活错误处理程序。指定的 line 必须在一个过程中,这个过程与 On Error 语句相同; 否则会发生编译时间错误
On Error Resume Next
说明当一个运行时错误发生时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行。访问对象时要使用这种形式而不使用 On Error GoTo
On Error GoTo 0
禁止当前过程中任何已启动的错误处理程序
1. On Error GoTo line
本语句表示当程序错误时为VBA指定一个回应方式,如果有错误就跳转到指定的标签处继续运行。
通常见指定的标签包括三类:
对错信息进行重新描述
指定错误的处理方式
循环执行等待消除错误
下针对以上三类应用各举一个实例进行讲解。
实例一:将当前选择的图片扩大到2倍。
Excel 2003和2007在图片缩放上有一些差异,开发者需要明白这一点。即2007中缩放图片时是按锁定纵横比的前提下执行的,而2003中虽然图片的属性默认也是锁定纵横比,但是利用代码来进行操作时却没有按锁定纵横比的方式进行。估计微软意识到这个BUG,在2007做了改进。所以在Excel 2003和2007中可以使用以下两段代码实现?
Sub 将选择的图片放大到倍() 'Excel 2003专用
2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍
2, msoFalse, msoScaleFromTopLeft '高度增大到两倍
End Sub
先扩大宽度再扩大高度。
Sub 将选择的图片放大到倍() 'Excel 2007专用
2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍
End Sub
当宽度扩大后,高度也同时扩大。
当然,也可以利用IF将两个过程综合为一个,使用可以通用于Excel 2003和2007,代码如下:
Sub 将选择的图片放大到两倍() '通用
2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍
IF * 1 < 12 Then '版本低于12才执行
2, msoFalse, msoScaleFromTopLeft '高度增大到两倍
End IF
End Sub
如果选择工作表中某个图片,执行以上代码后,那么图片是自动放大至两倍。然而,当用户选择单元格再执行过程时,将弹出图所示对话框。显然从这个错误提示很难明白出错的真正原因,完全有必要对这个错误提示进行重新描述,让用户更清楚为何会出错。那么将On Error GoTo line加入代码中即可达成这种需求。代码如下:
Sub 将选择的图片放大到两倍B()
On Error GoTo err ‘防错处理
2, msoFalse, msoScaleFromTopLeft '宽度增大到两倍
IF * 1 < 12 Then '版本低于12才执行
2, msoFalse, msoScaleFromTopLeft '高度增大到两倍
End IF
Exit Sub '退出过程,防止正常情况下执行该标签中的语句
err:
MsgBox "请选择图片", vbExclamation, "出错"
End Sub
在以上代码中,首先指定如果程序出错就执行“Err”标签后的语句,然后对选择的图片进行缩放。如果用户选择的是图片,那么就图片缩放后退出程序;如果用户选择图片以外的任意对象,那么会直接跳转到Err标签后的语句,其错误示相对原来的信息更明确,见图所示。
图 选择单元格执行过程时的错误提示 图 重新描述错误信息
本例文件参见光盘:..\ 第十章\对错信息进行重新描述..xlsm
实例二:新建总表
新建工作表且命名为“总表”,在这种操作中,如果工作簿中已经存在某个表名为“总表”,那么程序一定会出现编号为1004的运行时错误,同时中断程序,见图所示。为了防止这种错误,可需要在代码中加入防错机制。代码如下:
Sub 新建总表()
On Error GoTo err '防错处理
after:=Sheets() '在最右边新建一个工俾作表
Sheets().Name = "总表" '将新工作有重命为“总表”
Exit Sub '退出程序
err: '指定出错时的处理方式,只要在工作簿中已有一个“总表”才会出错
= False '不出现内部提示框(删除工作表时的询问对话框)
Sheets().Delete '删除新建的工作表
= True '恢复提示
End Sub
过程中指定了程序出错时的处理方式——删除新建的工作表。
图 工作表重名错误
本例文件参见光盘:..\ 第十章\指定错误的处理方式.xlsm
实例三:选区求和或者求积
为了提升代码执行速度,声明变量时都尽量使用可用但占用空间最小的数据类型,然而如果用户输入数据超过它的有效范围,那么程序就会产生溢出错误。利用VBA的防错机制可以让用户重新输入以下新的数字。例如本例要求用户输入1时执行求和,输入2时执行求积,变量的数据类型为Byte,如果输入输入了300,那么防错机制就会启动,让用户输入准确的数据。完整代码如下:
Sub 选区求和或者求积A()
On Error GoTo Star '出错时返回Star标签处
Dim ans As Byte
Star: '设定标签
'如果有错误就提示
IF > 0 Then MsgBox "只能输入1或者2"
'让用户选择汇总方式
ans = ("输入1:对选区求和" & Chr(10) & "输入2:对选区求积", , , , , , , 1)
IF ans = 1 Then '如果输入1
MsgBox (Selection) '求和
ElseIF ans = 2 Then '如果输入2
MsgBox (Selection) '求积
End IF
End Sub
执行以上过程时将弹出一个“输入”对话框,假设用户故意输入300,见图所示,那么程序会返回标签Star处,首先弹出图所示提示,然后弹出图所示对话框要求用户再次输入数值,从而解决问题。
图 确定汇总方式 图 出错提示
VBA的错误处理程序有一个特点:如果在错误处理程序处于活动状态时(在发生错误和执行 Resume、Exit Sub、Exit Function 或 Exit Property 语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误。所以上面的过程也只能发生一次作用,即第一次输入300时可以返回标签Star处要求用户重新输入数据,但如果用户再次输入300,On Error语句将不再发生作用。为了解决这个问题,可以让过程递归。修改后的代码如下:
Sub 选区求和或者求积B()
Dim ans As Byte '声明一个动态变量
Static i '声明一个静态变量
i = i + 1 '将变量累加1
On Error GoTo Star: '出错时执行Star标签处的语句
IF i > 1 Then MsgBox "只能输入1或者2" '如果变量i大于1(只有出程序出错而再次调用过程自身才会出错)
'让用户选择汇总方式
ans = ("输入1:对选区求和" & Chr(10) & "输入2:对选区求积", , , , , , , 1)
IF ans = 1 Then '如果输入1
MsgBox (Selection) '求和
ElseIF ans = 2 Then '如果输入2
MsgBox (Selection) '求积
End IF
i = 0 '将静态变量还原为0,防止正常情况下也提示“只能输入1或者2”
Exit Sub '退出过程
Star: '标签,让过程调用自身,重新让用户输入数值
选区求和或者求积B
End Sub
如果在过程中输入以了超过Byte有效范围的数值,那么过程会一直循环下去,弹出对话框让用户重新输入数值,直到输入1或者2为止。
本例文件参见光盘:..\ 第十章\循环执行等待消除错误.xlsm
2. 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语句在实际工作中的应用。
实例一:计算人均款项
在图所示的工作表中,需要根据预拨款和部门的人数计算人均预拨款。然而部分部门未上报人数,在编写代码时必须要防错,否将产生“除数为0”的错误。代码如下:
Sub 人均预拨款()
'遇到错误时继续执行,防止人数未填时产生错误、中断程序
On Error Resume Next
Dim Item As Byte '声明变量
For Item = 2 To Cells(, 1).End(xlUp).Row '遍历所有部分
Cells(Item, 4) = Cells(Item, 1) / Cells(Item, 3) '计算人均款项
Next Item
End Sub
执行以上代码时,过程会避过人数为0的部门,而继续计算下一部门的人均款项,见图所示。
图 预算表 图 计算要均预算
本例文件参见光盘:..\ 第十章\防错处理一.xlsm
实例二:打印所有工作表的打印区域
逐个打印当前工作簿中所有工作表的打印区域,如果某个工作表未指定打印区域PrintArea,那么程序会中断,且弹出图所示错误。如果利用防错语句就可以让程序忽略该表,继续打印下一个工作表。完整代码如下:
Sub 打印所有工作表数据()
On Error Resume Next '错误时执行下一步
Dim sht As Worksheet '声明一个工作表对象变量
For Each sht In Sheets '遍历所有表
'打印每个工作表的打印区域,如果某工作表未指定打印区域,会产生错误
().PrintOut
Next sht
End Sub
执行以上过程不会产生可错误,可以按顺序将已设置打印区域的所有工作表数据打印出来。如果所有工作表都未指定打印区域则程序无任何反应。
图 工作表未指打印区域时产生的错误提示
本例文件参见光盘:..\ 第十章\防错处理二.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)
Filename:="C:\" & arr(Item) '逐个打开工作簿
IF = 1004 Then msg = msg & arr(Item) & Chr(10) '如果存在错误则记录当前工作簿的名称
'清除错误设置,否则以后所有工作簿的名字都会被记录下来
Next Item
MsgBox msg '报告未打开的工作表名称
End Sub
假设C盘中只在“一月生产表.xlsm”和“三月生产表.xlsm”那么执行该过程时会产生图所示对话框,罗列出所有不存在的工作簿名称。
图 罗列不存在工作簿名称
在本例中,有一个地方需要特别注意:假设第一个工作簿“一月生产表.xlsm”不存在,那么在以后的循环中,不管什么时候“ = 1004”都成立。那么对于已经打开的工作簿也会将其名称罗列在对话框中,这显然不是用户期望的结果。为了防范此问题,需要利用Err对象的Clear方法将其归零,然后再继续错误编码地捕捉。
本例文件参见光盘:..\ 第十章\防错处理三.xlsm
3. On Error GoTo 0
On 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语句,表示清除前面的错误处理机制。
错误处理:错误三次则退出程序
计算机的登录密码可以设置为错误N次即禁止登录,而VBA中利用错误处理规则也可以完成同等功能。
现设计一个打开文件时密码错误三次则退出程序的功能。该文件为C盘中的“生产表.xlsx”,密码为123。具体步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口录入以下代码:
Sub 错误三次则退出()
Star: '指定标签,错误时从头开始执行
On Error Resume Next '错误时执行下一步
'清除错误(不等于On Error GoTo 0,请注意)
i = i + 1 '累加变量,记录输入密码的次数
'输入密码,在对话框中提示次数
pass = ("请输入密码:" & Chr(10) & "您还有" & 4 - i & "次机会", "第" & i & "次输入密码", , , , , , 3)
'利用前面输入的密码去打开带有密码的工作簿
"C:\", , , , pass
'如果已经错误三次则提示并结束程序,您也可以加入关闭工作簿或者退出Excel程序等等语句
IF i = 3 Then MsgBox "对不起,你已错误三次,程序即将关闭": End
IF Err <> 0 Then GoTo Star '如果有错误则返回标签Star处重新执行
End Sub
(3)在C盘存放一个打开密码为123的文件——生产表.xlsx;
(4)光标定位于“错误三次则退出”过程任意位置处,按下快捷键【F5】执行程序,程序立即弹出图所示对话框,在对话框中提示了用户当前是第几次输入密码,以及还剩下几次机会;
(5)如果在对话框输入密码456,并单击“确定”按钮将弹出图所示对话框。
图 第一次录入密码 图 第二次录入密码
(6)如果第三次输入错误的密码将弹出图所示对话框,同时表示这是最后一次机会。如果第三次错误则弹出图所示错误信息。
图 第三次输入密码 图 密码错误三次后的错误信息
在本例中,与“循环执行等待消除错误.xlsm”工作簿中的过程“区域求和或者求积B”在功能上相似,但实现方式上采用了完全不同的思路,不再用静态变量和过程递归,而是在过程最前端设置标签,程序中的防错语句将过程跳转到该标签即可,因过程不曾结束,变量的值不会被释放,所以动态变量也可以实现错误次数的记录。
本例文件参见光盘:..\ 第十章\错误三次则退出程序.xlsm
错误处理:多功能选区统计
Excel 2003的状态栏可以对选区的平均值、计数、计数值、最大值、最小值及求和进行统计,然而却无法同时在状态栏显示出来。它只同由用户指定一种统计类型,见图所示;Excel 2007对此做了改进,可以同时显示六种结果在状态栏,见图所示。然而当用户需要显示统计结果的同进还要将统计导入到单元格中那么只能利用VBA程序了。
图 Excel 2003调整状态栏显示信息 图 Excel 2007同时显示多项统计结果
实现要例需求步骤如下:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口录入以下代码:
Sub 多功能计算()
Dim MSG As String, aver, rng As Range
On Error GoTo ERR '如果程序出错,就跳转至ERR标签处,运行ERR之后的语句
MSG = MSG & "计数值:" & (Selection) & Chr(10)
MSG = MSG & "计 数:" & (Selection) & Chr(10)
MSG = MSG & "最大值:" & (Selection) & Chr(10)
MSG = MSG & "最小值:" & (Selection) & Chr(10)
On Error Resume Next '重新指定新的防错机制
aver = (Selection) '获取平均值
'如果有错误(选区空白或者没有数值时才产生错误)则平均值等于0,否则等于平均值
MSG = MSG & "平均值:" & IIF( = 1004, 0, aver) & Chr(10)
MSG = MSG & "合 计:" & (Selection) & Chr(10)
MSG = MSG & "单元格:" &
MsgBox "您的选区:" & Chr(10) & MSG, 64, "【友情提示】" '弹出提示
Set rng = ("请选择存放结果的单元格", "选择单元格", , , , , , 8)
IF Not rng Is Nothing Then rng(1) = MSG
Exit Sub '退出程序,避免运行ERR标签后的语句
ERR: '设定一个标签,必须加冒号,但标签名不能与程序名称相同
MsgBox "请选择单元格。", 64, "提示"
End Sub
(3)返回工作表界面,选择数据区域,按下快捷键【Alt+F8】打开“宏”对话框,然后执行过程“多功能计算”,程序将根据选区的数据弹出图所示统计结果;
图 根据选区数据进行多功能统计
(4)单击“确定”按钮后,程序会继续弹出图所示对话框,提示用户选择存放结果的单元格。如果不需要显示在单元格,那么可以单击“取消”按钮,否则可以选择任意一个单元格存放。用户可单元格一个单元格也可以是选择一个区域,但结果只存入该区域左上角单元格。本例中选择Sheet2工作表的A1单元格,然后单击“确定”按钮,执行结果见图所示。
图 选择存放结果的单元格 图 将结果存入单元格
本例过程中有五个知识点需要注意:
(1)程序对选区进行统计,如果用户选择了单元格以外的任何对象,那么程序将产生编码为1004的错误,所以需要利用防错语句On Error GoTo ERR提示用户选择单元格;
(2)On Error GoTo ERR仅仅对它后面的第一句代码产生作用。如果第一句没有出错,那么第二句、第三句也不可能出错;
(3)利用平均值函数对选区计算平均值时,如果选区空白,或者只有文本,即使用选择对象是单元格,“(Selection)”语句也同样会产生错误。此时需要改变已有的防错机制,强制平均值返回零,而非提示用户“选择单元格”。所以利用“On Error Resume Next”语句让过程继续执行,如果有错误就返回0,否则返回平均值;
(4)在选择结果存放的单元格时,用户有可能选择一个区域,那么结果会存入该区域所有单元格,在过程中有必要限制对第一个单元格赋值,其它单元格忽略。
(5)如果还需要追求完善,那么可对过程继续改进。即假设用户选择了在非空单元格,那么提示用户是否覆盖原有数据,如果用户选“否”则退出程序,选择“是”则覆盖数据,这个就交给读者去思考吧。
本例文件参见光盘:..\ 第十章\多功能选区统计.xlsm
错误处理的作用域
错误处理语句On Error GoTo line、On Error Resume Next、On Error GoTo 0也存在作用域,不过通常只在当前过程与被调用的过程间来讨论,而非当前过程与其它不相关的过程或者模块与模块之间讨论作用范围。
Error GoTo line
On Error GoTo line的作用范围是当前过程,即它的目标标签必须在当前过程中,否则将产生“标签”未定义的编译错误。
2. On Error Resume Next
On Error Resume Next的作用范围视它的出现位置而定,当过程中调用了其它过程时,如果错误处理语句在当前过程中,且位于Call语句前,那么它对当前过程生效,也对其它被调用的所有过程都生效。例如以下A、B、C三个过程,A过程中调用了B和C过程,A过程中的On Error Resume Nex语句可以屏弊A、B和C过程中所有错误。
Sub A()
On Error Resume Next
= ""
Call B
Call C
End Sub
Sub B()
= ""
End Sub
Sub C()
= ""
End Sub
执行过程A,它不会产生任何错误,表示On Error Resume Nex语句对A、B、C都产生了作用。
如果On Error Resume Nex语句处于被调用的过程中,那么它只对被调用的过程生效,无法控制主程序和后面被调用的过程。例如:
Sub A()
Call B
Call C
= ""
End Sub
Sub B()
On Error Resume Next
= ""
End Sub
Sub C()
= ""
End Sub
在以上三个过程中,过程B中的防错设置仅仅对过程B生效,当返回过程A后,不管是过程A中的错误还是被调用的过程C中的错误都不会处理。
根据以上分析,如果三个过程都需要处理错误,那么只能主程序中设置,其调用的子过程中会延用主程序中的错误处理机制。
然而当主程序与其调用的子过程中需要不同的处理方式时,那么每个子过程需要清除主程序中的防错机制,然后设置自己的处理方式。
另外还有一种处理方式:On Error GoTo line与Resume Next共用,这是多过程相互调用的多需要用到的一种方式。
例如以下三种过程,主过程A调用了B和C,而三个过程都具有错误,都可以引用错误处理语句On Error GoTo err。然而事实上在执行主过程A时,当程序出错后会跳转到Err标签处执行错误处理语句,却会忽略调用的过程B和C。
Sub A()
On Error GoTo Err
= ""
Call B
Call C
Exit Sub
Err:
MsgBox "不能以空文本对工作表命名"
End Sub
Sub B()
= ""
End Sub
Sub C()
= ""
End Sub
如何解决上述BUG?可以利用Resume Next语句,表示继续下一步。
为了让错误处理的提示更易懂,下面的三个实例中采用了不同的方式对工作表进行命名:
Dim str As String
Sub A()
On Error GoTo err
str = "?"
= str
Call B
Call C
Exit Sub
err:
MsgBox "不能以" & str & "对工作表命名"
Resume Next
End Sub
Sub B()
str = "/"
= str
End Sub
Sub C()
str = "*"
= str
End Sub
在以上三个过程中,仅仅在主过程A中调置了防错处理,但它可以对自身和所有被调用的子过程生效。最重要的是当主过程中“ = str”语句产生错误时,它会引发标签Err之后的执行语句,而后会返回刚才出错的语句“ = str”,紧接着执行一下句代码“Call B”,从而让三个过程中的错误信息都可以显示出来。分别如图、图和图所示。
图 过程A的提示信息 图 过程B的提示信息 图 过程C的提示信息
如果在主过程中没有“Resume Nex语句”,将不会执行过程B和C。
本例文件参见光盘:..\ 第十章\错误处理的作用域.xlsm
Error GoTo 0
On Error GoTo 0语句不管放置在什么位置,它只能对当前过产生作用,自调用自己的过程或者被自己调用的过程都没有任何影响。
在以下三个过程中,主过程指定了一个标签,当主程序和它的任何子过程产生错误时都可经跳转到这个标签后开始执行。在本例过程B中利用On Error GoTo 0语句虽然可以清除错误设置,然而它对主过程A及不产生任影响,所以过程的执行结果和没有加On Error GoTo 0时完全一样。
Dim str As String
Sub A()
On Error GoTo err
str = "?"
= str
Call B
Call C
Exit Sub
err:
MsgBox "不能以" & str & "对工作表命名"
Resume Next
End Sub
Sub B()
On Error GoTo 0
str = "/"
= str
End Sub
Sub C()
str = "*"
= str
End Sub
GoSub...Return语句
GoSub...Return语句用于在一个过程中满足条件时则跳转到设定的标签处执行,执行后再返回条件语句之后继执行。与Goto Line配合Resume next的功能相似。
其语法如下:
GoSub Line
...
line
...
Return
GoSub Line与Return必须在同一过程中,不能跳转到其它过程中。可以设置一个标签行,但有多个GoSub Line都指向这个标签。
以下的案例可能会加深用户对GoSub...Return语句的理解。
某成绩表中包括四个班级的成绩,为了检查是否有某班某人的成绩还没有上报,利用VBA对每个班级进行检查,如果某班所有人的成绩都有则在工作表名后添加“已OK”,否则对缺少成绩分数的单元格进行灰色底纹标示。完整代码如下:
Sub 检查资料是否完整()
'功能:逐个检查工作表,如果有空白区则用灰色标示出来,如果不存在空白区则对工作表重命名,加“已OK”
Dim rng As Range
'遇到错误时继续下一步
On Error Resume Next
Dim sht As Worksheet
For Each sht In Sheets '遍历所有工作表
'将选区中所有单白单元格赋与变量Rng
Set rng = (xlCellTypeBlanks)
= 15 '将Rng填充灰色背景
IF Err <> 0 Then GoSub Err '如果有错误则执行Err标签后的语句
Next '循环,检查下一个单元格
Exit Sub '退出程序,避免执行后面的标签行
Err: '设置一个标签
= & "(已OK)" '将不存在空白区的工作表名添加“已OK”
'清除错误
Return '返回到gosub语句之后继续执行
End Sub
以上过程中利用SpecialCells方法定位已用区域的空白单元格,如果工作表中未缺少任意学生的成绩(即已用区域没有空单元格)则会产生错误,那么通过GoSub语句将程序引导到Err标签处,对工作表进行重命名,完成后再通过Return返回,继续检查下一个工作表。执行完毕后,其效果见图所示:
图 成绩表 图 添加标识
本例文件参见光盘:..\ 第十章\ gosub示例.xlsm
本章对VBA中的各种语法进行了详细地介绍,以及通过大量实例传授各种语句的应用技巧。事实上VBA中还有很多不常用的语句,用户需要通过实践去摸索它们的技巧,例如Get语句、Deftype语句、Property Let语句、 While...Wend 语句、Input # 语句……
开发错误处理函数
在编写程序时,常常需要在多个过程中编写进行类似的防错代码。为了简化程序可以编写一些函数,在函数中设置好所有防错机制,并在其它程序中调用即可。
本节开发四个关于错误处理的函数: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") Then
With
.Name = "AA"
End With
End IF
End Sub
在过程中首先利用IsSht函数判断表为“AA”的工作表中否在,不存在则新建,且命名为“AA”。
2.判断工作簿是否存在
开发一个判断是否已经打开批定名称工作簿的自定义函数,若已打开则返回True,否则返回False。代码如下:
'声明一个Function过程,判断工作簿是否存在
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
该函数可以判断指定名称的工作簿是否打开,具有一个必选参数,结是为True或者False。
可以利用以下过程测试该函数:
Sub 打开工作簿()
'调用函数,判断是否已经打开“”工作簿,没有则打开"C:\"
IF Not IsWkb("") Then
"C:\" '打开工作簿
End IF
End Sub
3.判断批注是否存在
开发一个判断单元格是否有批注的函数,若有批注则返回True,否则返回False。代码如下:
'声明一个Function过程,判断工作簿是否存在
Function IsCom(rng As Range) As Boolean
'防错
On Error Resume Next
'设置单元格的批注的可见性,但并不改变其状态
'如果不存在批注则会产生错误
rng(1). = rng(1).
'函数的结果由是否存在错误来决定,没有错误则表示已具有批注
IsCom = (Err = 0)
End Function
该函数可以判断单个单元格是否有批注,具有一个必选参数,结是为True或者False。
可以利用以下过程测试该函数:
Sub 读取批注()
'读取批注前调用自定义函数判断是否存在
IF IsCom([a1]) Then MsgBox [a1].
End Sub
在读取单元格的批注前调用自定义函数判断是否存在批注。
4.判断单元格是否处于可操作状态
有很多操作都受工作表保护和单元格合并影响,例如筛选、排序、填充等等。只要工作表保护或者区域具有合并单元格,那么过程执行时一定会出错。现开发一个函数对区域进行判断,如果返回True则表明可操作。完整代码如下:
'声明一个Function过程,判断区域是否处理可操作状态
'可操作包括“排序”、“筛选”、“填充”等等受单元格合并及工作表保护影响的所有操作
Function IsOK(rng As Range)
'函数的值由工作表受保护状态与单元格区域合并属性两个值的乘积决定
'只要两者皆为False时,相加才会等于0,
'函数结果为true表示工作表未保护,也不存在合并区域
IsOK = ( + = 0) '此处Parent代表工作表
End Function
以上过程用于判断区域(可以是当前表也可以是其它工作表)是否受保护及存在合并单元格,有一个必选参数,结果为True或者False。
可以利用以下过程测试该函数:
Sub 填充A1到A7()
'检查工作表中指定区域是否OK
IF IsOK(Sheets(2).Range("a1:a7")) Then
'填充
Sheets(2).[a1].AutoFill Destination:=Sheets(2).Range("A1:A7"), Type:=xlFillDefault
End IF
End Sub
不管当前表是否Sheets(2)都可以进行操作。在测试时读者应尽量在保护与不保护或者合并与不合并下进行测试、比较。
本例文件参见光盘:..\ 第十章\错误处理函数.xlsm
第十一章
Excel常见对象的应用技巧
VBA中有两百多种对象,掌握VBA对象的属性与方法是成为VBA高手的必经之路。本例通过大量的实例对VBA中常见对象进行实例应用演示,从而提高读者驾驭对象的能力。
本章要点:
Application应用案例
Range对象应用案例
Names对象应用案例
Comments 对象应用案例
Sheets对象应用案例
Workbooks对象应用案例
Windows 对象案例
Application应用案例
Application对象代有Excel应用程序,本节从多方面对该对象进行实例演示。
选区拼写检查
〖案例要求〗:对选区中所有单词进行拼写检查,对错误者突出显示。
〖知识要点〗:CheckSpelling
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 单词检查()
Dim rng As Range
'遍历选区与已用区域的交集
For Each rng In (Selection, )
'首先将单词转换成首字母大写,然后在默认词典中检查,如果不存在
IF Not (StrConv(rng, vbProperCase)) Then
'将单元格背景设置为灰色
= 15
End IF
Next
End Sub
在本过程中,要求仅仅检查单词书写是否正确,忽略大小写问题,所要利用StrConv函数将单元格的所有单词规范化,转成首字母大写再进行拼写检查。
(3)返回工作表,选择工作表中所有单词;
(4)使用快捷键【Alt+F8】执行过程“单词检查”,执行过程前后的工作表状态见图和图所示:
图 待检查拼写的单词 图 对错误单词着色
〖语法补充〗:
(1) 方法用于对单个单词进行拼写检查。其语法如下:
(Word, CustomDictionary, IgnoreUppercase)
三个参数的含义见表11-1所示:
表11-1 CheckSpelling参数详解
名称
必选/可选
数据类型
描述
Word
必选
String
(仅应用于 Application 对象)要检查的单词
CustomDictionary
可选
Variant
一个字符串,它表示自定义词典的文件名,如果在主词典中找不到单词,则会到此词典中查找。如果省略此参数,则使用当前指定的词典
IgnoreUppercase
可选
Variant
如果为 True,则 Excel 忽略所有字母都是大写的单词。如果为 False,则 Excel 检查所有字母都是大写的单词。如果省略此参数,将使用当前的设置
在本例中,忽略了第二和第二参数,表示使用系统默认词典,且使用默认方式处理大小写问题。
(2)CheckSpelling方法不能单独使用,需要前置对象Application才可以正常使用。
本节关于Application对象的所有实例代码请参见光盘:..\ 第十一章\
调用工作表函数
〖案例要求〗:对选区数据汇总。
〖知识要点〗:WorksheetFunction
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 汇总选区()
'先检查选择的对象是否是单元格
IF TypeName(Selection) = "Range" Then
'如果是单元格则汇总
MsgBox (Selection)
End IF
End Sub
在本过程中,对选区进行计算之前首先判断选择对象是否单元格。因为用户可能选择了图片或者图表等等其它对象状态下运行本过程。加入IF判断对象的类型可以防错。
在VBA中可以利用循环对区域中所有数据汇总,然而当区域较大时,循环的方式效率太低,而通过方法调用工作表函数则可以一步完成。
(3)返回工作表,选择待计算的数据区域,利用快捷键【Alt+F8】打开宏对话框,选择并执行执行过程“汇总选区”,程序将弹出选区的计算结果。如果选择的对象不是单元格,那么执行程序时不会产生任何反应。
〖语法补充〗:
(1)可以返回一个WorksheetFunction 对象,调用工作表函数。
(2)WorksheetFunction属于Application对象的属性,但是在书写时可以忽略Application,只写WorksheetFunction即可。
(3)WorksheetFunction方式只能调用大部分工作表函数,部分函数无法调用。因为VBA自身提供了类似的函数替代,例如:
Sqrt函数、Int函数、Abs函数、Cell函数等等。其中VBA中的Spr、Int和Rnd分别取代了Sqrt函数、Int函数和Abs函数,而Cell函数的功能则可以通过获取对象的属性来实现。
切换鼠标形状
〖案例要求〗:将工作表中的十字光标恢复为箭头型光标。
〖知识要点〗:Cursor
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 恢复箭头光标()
= xlNorthwestArrow
End Sub
(3)按下快捷键【F5】执行该过程,返回工作表后,光标在工作表中移动时会显示箭头形状,而非十字型。图是执行过程前后光标的对比。
图 改变光标前后
〖语法补充〗:
(1)属性用于改变工作表中光标的形状,它有四个可选常量,常量与光标样式对应关系见表11-2:
表11-2 Cursor常量与形状
XlMousePointer 常量
形状
xlDefault
xlIBeam
xlNorthwestArrow
xlWait
(2)当宏停止运行时,Cursor 属性不会自动重设。但是Excel重启后光标会返回十字形。
(3)Cursor必须使用前置对象Application。
计算表达式
〖案例要求〗:将单元格中的文字表达式回转换成值,本例根据图所示产品规格计算体积。
〖知识要点〗:Evaluate
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 将表达式转换成值()
'声明一个变量,用于循环区域
Dim rng As Range
'遍历待计数区域
For Each rng In Range("A2:A4")
'在单元格的右边一个单元格返回计算结果
(0, 1) = ()
'计算下一个
Next rng
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将表达式转换成值”,A列中每个表达式瞬间转换成值,见图所示。
图 产品规格 图 将表达式转换成值
〖语法补充〗:
(1) 方法用将一个名称转换为一个对象或者一个值。它的具体语法如下:
(Name)
其中参数Name可以是用户定义的名称,也可以是一个运算表达式。
(2)也可以利用一个公式做为参数,例如:
("Sqrt(sum(a1:b2,D10+5)/10)")
也可以在参数中使用变量,例如:
Sub 开方()
Item = 25 '为变量赋值
MsgBox ("Sqrt(" & Item & ")") '利用变量与函数构造表达式
End Sub
(3)Evaluate可以单独使用,忽略其对象Application。
禁用程序运行时弹出警告框
〖案例要求〗:将A列中相同且相邻的单元格合并,合并时不能弹出提示框。
〖知识要点〗:DisplayAlerts
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 合并A列相同且相邻的单元格()
Dim rng As Range, rg As Range, rngs As Range
= False '禁止提示
'提取A列与已用数据区域的交集
Set rngs = (, [a:a])
Set rg = rngs(1) '将交集第一个单元格赋与变量rg
For Each rng In (1, 0).Resize(, 1) '在交集向下偏移一行的区域中循环
IF rng <> (-1, 0) Then '如果对象变量rng与其上一个单元不相等
Range(rg, (-1, 0)).Merge '合并对象变量前面的相同数据区域
Set rg = rng '重新指定对象变量
End IF
Next
= True '还原提示
rngs(1).Select '选择原选区中第一个单元格
End Sub
在本过程中首先将DisplayAlerts属性设置为False,可以禁止合并非空单元格时弹出警告框。在过程执行完毕时再将该属性还原为True。
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并A列相同且相邻的单元格”,图中所有相邻且相同的单元格瞬间合并,结果见图所示。
图 省市列表 图 合并相同且相邻的单元格
〖语法补充〗:
(1) 属性用于控制执行宏时是否弹出警告框。当值为True时允许弹出警告框,否则禁止弹出警告框。
(2)在Excel中可以弹出警告框的操作很多,例如合并非空单元格,删除非空工作表、未保存在退出程序、覆盖已有工作簿、将数据移动到非空区域等等。
(3)DisplayAlerts可以单独使用,忽略其对象Application。
调整计算方式
〖案例要求〗:每行插入空行。
〖知识要点〗:Calculation
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 隔行插入行()
Dim i As Integer, tim As Long
= xlManual '将计算方式改为手动
tim = Timer '记录当前时间
'遍历已用区域
For i = To 2 Step -1
Cells(i, 1). '每行前插入空行
Next i
= xlAutomatic '将计算方式改为自动
MsgBox Format(Timer - tim, "") & "秒" '记录时间10秒
End Sub
(3)假设工作表中在1000行数据,每行数据最后都在一个公式汇总该行的数据,那么插入以上代码,每行前插入一个空行,在笔者的电脑上时间约6秒;如果将Calculation语句删除再执行过程,那么它的执行时间达到10秒钟,且公式越复杂,该执行时间越长。
〖语法补充〗:
(1) 属性代表计算模式,它有三个可选项,三个内置常量及其含义见表11-3所示:
表11-3 Calculation常量列表
名称
值
描述
xlCalculationAutomatic
-4105
Excel 控制重新计算
xlCalculationManual
-4135
用户请求时进行计算
xlCalculationSemiautomatic
2
Excel 控制重新计算,但忽略表中的更改
(2)只要对工作表编辑,工作表中的每个公式都会重新运算一次,从而浪费程序的执行时间。所以在开发VBA过程时,需要将Calculation 属性设置为手动,程序执行完毕后再恢复即可。如此可以避免不必要的公式计算。当然工作表中不存在任何公式时可以例外。
(3)Calculation可以单独使用,忽略其对象Application。
罗列最近使用过的文件
〖案例要求〗:单击Excel 2007的Office按钮时,可以看到图所示的“最近使用的文档”,根据用户的设置不同,该文件的数量也有所差异。而在Excel 2003中,“最近使用的文档”罗列在文件菜单中。现要求将最近打开过的所有文件名包括路径罗列在工作表中,且需要显示其完整路径。
〖知识要点〗:RecentFiles
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 罗列最近打开过的文件()
Dim i As Byte
'遍历所有文件
For i = 1 To
'在单元格中罗列每个文件的详细路径与文件名
Cells(i, 1) = (i).Path
Next
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“罗列最近打开过的文件”,将在工作表A列中罗列出所有历史文件列表。图是Office按钮中的显示效果,而图是VBA过程的执行结果
图 Office按钮下拉菜单中的文档列表
图 VBA过程列出的文档列表
〖语法补充〗:
(1)表示最近打开过的文件。
(2)利用Item可以访问最近打开过的文件的每个子对象,而配合Open方法可以将某个位置的文件打开。例如打开最过打开的文档列表中第三个文件打开,可以使用以下语句:
(3).Open
(3)RecentFiles必须添加前置对象Application才可以使用。
查找并打开文件
〖案例要求〗:让用户选择文件,然后打开所选文件。
〖知识要点〗:FindFile
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 打开文件()
'弹出一个对话框,让用选择选择待打开的文件,如果选择了文件则打开,否则退出程序
IF Not Then End
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开文件”,将弹出一个“打开”对话框,可以从对话框中选择一个或者多个文件,VBA会将用户选择的文件打开。如果用户单击“取消”按钮,则直接退出程序。
〖语法补充〗:
(1) 方法用于显示“打开”对话框,用户从对话框可以浏览任意文件夹中的任意个文件,VBA可以将选择的所有文件打开。
(2)FindFile不能单独使用,必须配合前置对象Application。
建立文件目录
〖案例要求〗:将文件夹中的文件建立目录,需要包含路径。
〖知识要点〗:FileDialog
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 建立文件目录()
'声明变量
Dim i As Integer, FilesCount As Integer
'弹出一个打开文件的对话框
With (msoFileDialogOpen)
.AllowMultiSelect = True '多选
.Show '显示对话框
FilesCount = . '统计用户选择的文件数量
'遍历所有选择的对象
For i = 1 To .
'将选择的文件名输出到单元格中
Cells(i, 1) = .SelectedItems(i)
Next
End With
End Sub
在该过程中“.AllowMultiSelect = True”语句表示允许用户同时选择多个文件,甚至可以使用快捷键【Ctrl+A】选择所有文件。
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“建立文件目录”,将弹出一个“打开文件”对话框。假设仅选择部分文件,见图所示,当单击“打开”按钮后,程序会将选择的对象全部罗列在工作表A列中,见图所示。
图 选择待建立目录的工作簿
图 在工作表建立文件目录
〖语法补充〗:
(1) 属性可以返回一个 FileDialog 对象,该对象表示文件对话框的实例。它的具体语法如下:
(fileDialogType)
其参数是代表对话框类型的常量,有以下四种类型可选:
表11-4 对话框类型常量表
名称
值
描述
msoFileDialogFilePicker
3
“文件选取器”对话框
msoFileDialogFolderPicker
4
“文件夹选取器”对话框
msoFileDialogOpen
1
“打开”对话框
msoFileDialogSaveAs
2
“另存为”对话框
(2)FileDialog(msoFileDialogOpen)仅仅让用户选择文件,并返回文件名与路径,并不能实际打开文件对象,如果配合方法就可以打开所有选择的文件对象。
(3)FileDialog必须配合前置对象Application才可以使用。
定制程序标题
〖案例要求〗:将Excel默认标题“Microsoft Excel”修改为“人事系统”。
〖知识要点〗:Caption
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 修改标题()
= "人事系统"
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“修改标题”,将Excel的默认标题将修改为“人事系统”,见图所示。
图 修改默认的程序标题
〖语法补充〗:
(1) 属性返回或设置一个String值,它代表出现在Microsoft Excel主窗口标题栏中显示的名称。如果使用Msgbox语句可以获取当前的标题字符串,而修改标题则用等号直接赋值即可。
(2)如果使用以下语句,无法取消Excel的标题,反而是恢复默认的标题“Microsoft Excel”:
= ""
如果需要Excel不显示任何标题,简单的方式是利用空格做为标题。
(3)Caption必须配合前置对象Application才可以使用。
打开指定应用程序
〖案例要求〗:打开内置的计算器。
〖知识要点〗:ActivateMicrosoftApp
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 打开计算器()
'打开Microsoft内置应用程序
Index:=0
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开计算器”,程序会立即打开Excel计算器,见图所示。
图 打开内置计算器
〖语法补充〗:
(1)方法可以激活一个Microsoft应用程序。如果该应用程序已经处于运行状态,则本方法激活的是正在运行的应用程序。如果该应用程序不处于运行状态,则本方法将启动该应用程序的新实例。例如已在打开一个WORD文件,那么本方法只能使Word窗口激活,若未打开Word程序,则可以开启一个新Word文档。它的具体语法如下:
(Index)
其中参数Index代表应程序名称,可以使用常量或者值。内置常量与见表11-5所示:
表11-5 内置应用程序常量
名称
值
描述
calc
0
计算器
xlMicrosoftWord
1
Microsoft Office Word
xlMicrosoftPowerPoint
2
Microsoft Office PowerPoint
xlMicrosoftMail
3
Microsoft Office Outlook
xlMicrosoftAccess
4
Microsoft Office Access
xlMicrosoftFoxPro
5
Microsoft FoxPro
xlMicrosoftProject
6
Microsoft Office Project
xlMicrosoftSchedulePlus
7
Microsoft Schedule Plus
(2)ActivateMicrosoftApp可以打开的应程序极其有限,如果需要打开更多的应用程序,应该使用Shell方法。例如打开DOS系统,可以使用以下过程:
Sub 显示Dos界面()
Shell ""
End Sub
显然它较ActivateMicrosoftApp方法灵活很多,在后续开发插件时还会涉及Shell的大量应用。
(3)ActivateMicrosoftApp必须配合前置对象Application使用。
新建一个带有7个工作表的工作簿
〖案例要求〗:新建一个工作簿,让其默认带有7个工作表。
〖知识要点〗:SheetsInNewWorkbook
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 新建一个带有7个工作表的工作簿()
'设置默认新工作簿的默认工作表数量为7
= 7
'新建一个工作簿
End Sub
该过程首先改变新工作簿中默认工作表数3为7,然后新建一个工作簿。
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建一个带有7个工作表的工作簿”,程序会新建一个带有7个工作表的工作簿。
〖语法补充〗:
(1) 属性可以返回或设置Excel自动插入到新工作簿中的工作表数目。其范围在1到255之间。
(2)如果需要以后新建工作簿时仍然是默认值3,那么需要过程的最后加一句代码,将该值设置为3。
(3)SheetsInNewWorkbook必须配合其前置对象Application同时使用。
在指定时间提示行程安排
〖案例要求〗:在下午13:30时提示“会议时间到”,并在10分钟后关闭工作簿。
〖知识要点〗:OnTime\、Quit
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 会议提示()
'设置13:30提示开会
TimeValue("13:30:00"), "提示"
'设置13:40退出工作簿
TimeValue("13:40:00"), "关闭工作簿"
End Sub
Sub 提示()
[a1] = "会议时间到!请准时参加。" '工单元格产生提示
Range("A1"). = 50 '为了醒目,将单元格字体加大到50
Columns("A:A").ColumnWidth = 148 '将A1列宽加大
Rows("1:1").RowHeight = 70 '将A1行高加大
End Sub
Sub 关闭工作簿()
'关闭警告提示 ,否则会询问是否保存工作簿
= False
'退出Excel
End Sub
第一个过程属于主过程,用于设置某个时间执行某个程序,其它两个过程则为被调用的过程,会在指定的时间执行。
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“会议提示”,等到13:30后将会在工作表A1显示出 “会议时间到!请准时参加。”,而10分钟后会自动关闭Excel应用程序。
〖语法补充〗:
(1)方法可以安排一个过程在将来的特定时间运行(既可以是具体指定的某个时间,也可以是指定的一段时间之后)。它的语法如下:
(EarliestTime, Procedure, LatestTime, Schedule)
四个参数的含义见表11-6所示:
表11-6 OnTime参数详解
名称
必选/可选
描述
EarliestTime
必选
希望此过程运行的时间
Procedure
必选
要运行的过程名
LatestTime
可选
过程开始运行的最晚时间
Schedule
可选
如果为True,则预定一个新的OnTime 过程。如果为False,则清除先前设置的过程
其中第二参数用于指定待运行的过程名称,在指定过程名时必须使用引号;第三参数使用 Now + TimeValue(time)形式可安排从现在开始计时经过一段时间之后运行某个过程,而使用 TimeValue(time)形式则可安排某个过程只在指定的时间执行。
(2)如果需要中途取消计划的任务,那么可以将第四个参数设为False:
TimeValue("13:30:00"), "提示", , False
(3)如果需要在30秒钟后关闭工作簿,使用以下过程:
Sub 会议提示()
Now + TimeValue("00:00:30"), "关闭"
End Sub
Sub 关闭工作簿()
= False
End Sub
(4)方法用于退出Excel,它有别于方法。方法是关闭工作簿,但应用程序仍然处于打开状态,而则关闭工作簿的同时退出Excel应用程序。
模拟键盘快捷键
〖案例要求〗:打开高级选项。
〖知识要点〗:SendKeys
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 打开高级选项()
'模仿键盘操作: Alt -F + i + 四个下箭头
"%fi{DOWN 4}"
End Sub
(3)返回工作表,利用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开高级选项”,程序立即会开启Excel的高级选项。功能等能于快捷键【Alt+F+I+↓+↓+↓+↓+↓】(↓表示键盘中的下箭头)。
〖语法补充〗:
(1)方法可以将一个或多个按键消息发送到活动窗口,模仿键盘操作。其语法如下:
(Keys, Wait)
第一参数string表示要发送的按键消息;第二参数wait是可选参数,如果为 True,则Excel 会等到处理完按键后将控件返回给宏;如果为 False,则继续运行宏而不等到处理完按键。
(2)方法发送的信息包括快捷键和文字信息。例如在使用快键复制A1的值到B1,那么可以使用以下过程。
Sub 复选A1的值到B1()
[a1].Select
"^c{RIGHT}^v"
End Sub
首先激活A1,然后向Excel发送快捷键【Cltr+C】表示复制单元格,然后再用右箭头激活B1,最后使用【Ctrl+V】粘贴数据。
如果是发送字符到A1单元格,那么可以使用以下过程:
Sub 在A1输入短句()
[a1].Select
"{f2}how are you?~"
End Sub
执行以上过程后,A1中的短句可能如图所示,也可能如图、图所示,这取决于用户使用的输入法以及不同输入法中定义的词组。如果在英文状态下执行程序,会产生相同的结果,如果多人使用不同输入法,或者相同输入法但定义的词组不同也可以产生不同的结果。
图 英文状态下输入 图 五笔状态下输入 图 拼音状态下输入
(3)在VBA中,可以调用的、对应键盘上指定的功能按键包括以下表中的项目:
表11-7 SendKeys中的代码及其对象的功能按键
按键
代码
按键
代码
BACKSPACE
{BACKSPACE} 或 {BS}
向左键
{LEFT}
Break
{BREAK}
Num Lock
{NUMLOCK}
Caps Lock
{CAPSLOCK}
PageDown
{PGDN}
Clear
{CLEAR}
PageUp
{PGUP}
Delete 或 Del
{DELETE} 或 {DEL}
Return
{RETURN}
向下键
{DOWN}
向右键
{RIGHT}
End
{END}
Scroll Lock
{SCROLLLOCK}
Enter(数字小键盘)
{ENTER}
Tab
{TAB}
Enter
~(波形符)
向上键
{UP}
Esc
{ESCAPE} 或 {ESC}
F1 到 F15
{F1} 到 {F15}
Help
{HELP}
Shift
+(加号)
Home
{HOME}
Ctrl
^(插入符号)
Ins
{INSERT}
Alt
%(百分号)
除上表外,所有英文字母和数字也可以通过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 '声明变量
"12345" '先发送验证码再显示对话框
ans = ("请输入验证码", "权限", , , , , , 1) '将验证码送送到本对话框中
MsgBox ans
End Sub
执行以上代码时,虽然InputBox方法并未设置默认值,但是在其对话框中仍然产生默认值12345,这是过程中使用SendKeys方法的结果。
图 向对话框发送字符串
为程指定快捷键
〖案例要求〗:利用VBA对VBA过程设置快捷键。
〖知识要点〗:OnKey
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Private Sub 新建工作表()
End Sub
Sub 设定快捷键()
'对程序指定一个快捷键
"^q", "新建工作表"
End Sub
第一个过程是先进设置快捷键的过程,第二个过程用于对某个过程指定快捷键。
(3)光标置于过程“设定快捷键”并按下快捷键【F5】执行程序;
(4)返回工作表界面,使用快捷键【Ctrl+q】呼叫“新建工作表”过程,可以发现该快捷键已经产生效用,工作表簿中可以产生一个新工作表。
〖语法补充〗:
(1) 方法可以指定一个特定键或特定的组合键绑定到指定的过程。其语法如下:
Application..OnKey(Key, Procedure)
第一参数表示按键的字符串,第二参数表示要运行的过程名称的字符串。如果 Procedure 为空文本 (""),则按 Key 时不发生任何操作。
(2)Key参数的可用键与表11-7一致,可将其中的任意键绑定到一个过程。
(3)也可以使用OnKey将Excel内置的快捷键绑定到自己的程序中,例如使用快捷键【Ctrl+C】来执行过程“新建工作表”,那么可以使用以下过程:
Sub 设定快捷键()
'对程序指定一个快捷键
"^c", "新建工作表"
End Sub
(4)如果需取消绑定,那么可以将OnKey的第二参数使用空文本""。例如需要还原快捷键【Ctrl+C】给内置的复制功能,那么可以执行以下过程:
Sub 恢复()
"^c", ""
End Sub
(5)OnKey的另一个作用是屏弊Excel的某个内置功能。例如禁止用户使用【Ctrl+C】和【Ctrl+V】来复制、粘贴数据,那么可以使用以下过程来实现:
Sub 禁止复制与粘贴()
"^c", "禁止"
"^v", "禁止"
End Sub
Sub 禁止()
MsgBox "禁止使用本功能"
End Sub
执行过程“禁止复制与粘贴”后,快捷键【Ctrl+C】和【Ctrl+V】都已禁用,无法再利用它们来复制、粘贴数据。使用使用快捷键【Ctrl+C】或者【Ctrl+V】时都会弹出以下提示:
图 提示
当重启Excel后,方法设定的快捷键将会失效,所以如果需要某个指定的快捷键永远可用,可以将该过程加入Open事件中。
(6)OnKey必须配合其前置对象Application同时使用。
合并区域
〖案例要求〗:将区域中将包含“丽”的单元格填充红色,'将姓“刘”的姓名所在单元格填充黄色。
〖知识要点〗:Union
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 填充颜色()
'将包含“丽”的单元格填充红色
'将姓“刘”的姓名所在单元格填充黄色
Dim rng As Range, 丽 As Range, 刘 As Range
'遍历当前表已用区域
For Each rng In
IF rng Like "*丽*" Then '如果单元格中包括“丽”
IF 丽 Is Nothing Then '如果变量“丽”未初始化
Set 丽 = rng '将符合条件的单元格赋与变量“丽”
Else '否则
Set 丽 = (丽, rng) '合并第一个找到的单元格与符合条件其它单元格
End IF
End IF
IF rng Like "刘*" Then '解释同上
IF 刘 Is Nothing Then
Set 刘 = rng
Else
Set 刘 = (刘, rng)
End IF
End IF
Next
丽. = 3 '将符合条件的区域添加颜色
刘. = 6
End Sub
以上过程中使用遍历区域的方式逐个对比单元格字符,并利用Union方法将所找到的符合条件的所有单元格合并成一个区域,然后对该合并区域设置背景色。
(3)返回工作表界面,假设工作表中有图所示数据,那么使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“填充颜色”后,程序会将找到的符合条件的单元格标示为图所示背景色。
图 成绩表 图 对符合条件的姓名添加背景色
〖语法补充〗:
(1) 方法返回两个或多个区域的合并区域。其语法为:
(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) 方法无法合并跨表的区域。例如以下代码以一定会产生运行时错误:
Sub 跨表合并()
MsgBox (Sheets(1).[a1], Sheets(2).[d1:g2]).Address(0, 0)
End Sub
(3)Union可以单独使用,忽略其前置对象Application。
获取多区域的交集
〖案例要求〗:对工作表中列标题及行标题的字体加粗,再将标题以外的所有数据倾斜显示。其中标题的行数则用户手工指定。
〖知识要点〗:Intersect与InputBox
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 加粗与倾斜()
Dim 行标题 As Byte, 列标题 As Byte
'错误时继续执行
On Error Resume Next
Star: '设置一个标签
'用户指定行、列标题数量
行标题 = ("请输入行标题数", "标题行数", 2, , , , , 1)
列标题 = ("请输入列标题数", "标题列数", 1, , , , , 1)
'如果存在错误则返回重新输入
IF Err <> 0 Then MsgBox "只能输入1到255之间的值": : GoTo Star
'如果行标题大于总行数或者列标题大于总列数
IF 行标题 > Or 列标题 >= Then
'提示并返回,重新输入
MsgBox "不能超过已用区域": GoTo Star
End IF
Dim rng As Range
'利用变量简化输入
Set rng =
'对已用区域加粗
= True
With ((行标题, 列标题), rng).Font
.Bold = False '取消加粗
.Italic = True '倾斜
End With
End Sub
以上过程中首先让用户输入标题的行数、列数,考虑到用户输入值超过Byte范围的潜在错误,通过防错机制捕到错误时返回Star标签让用户重新录入,直到数值处于可接受的范围为止。
(3)返回工作表界面,假设工作表中有图所示数据,那么使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“加粗与倾斜”后,分别弹出两次对话框,让用户指定行标题与列标题。本例中使用默认值即可。设置OK后其结果将如图所示:
图 成绩表 图 加粗标题、倾斜成绩区域
〖语法补充〗:
(1)方法返回一个 Range 对象,该对象表示两个或多个区域重叠的矩形区域。它的语法如下:
(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)方法返回多区域的重叠区域,如果其参数不存在重叠部分则返回Nothing.
(3)Intersec方法可以单独使用,忽略其前置对象Application。
中断程序到一定时间后再继续
〖案例要求〗:某工作表数据在10分钟内一定会更新一次,现利用VBA完成每10分钟打印一次工作表数据,从而实现打印的资料自动更新。直到晚上20:00时停止打印。
〖知识要点〗:Wait
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 每10分钟打印工作表()
Dim item As Integer
'先打印一次工作表
'只要时间小于20:00就循环执行
Do While TimeValue(Now) < TimeValue("20:00:00")
'程序中断10分钟
(Now + TimeValue("00:10:00"))
'记录打印次数
item = item + 1
'打印当前工作表
Loop
MsgBox "已打印" & item & "次"
End Sub
本过程中利用TimeValue函数获取现在的时间值,再与指定的时间进行比较,如果小于晚上20点则循环执行过程,打印当前表数据。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“每10分钟打印工作表”,程序会马上打印一次当前表,然后每隔10分钟再打印一次,直到晚上20点。
〖语法补充〗:
(1)方法可以暂停运行宏,直到特定时间才可继续执行。它的语法如下:
(Time)
其中参数Time表示望宏继续执行的时间。
(2)Wait方法可以暂停Excel的所有操作,并且在Wait阶断可能禁止用户对计算机做其他操作。换一种说法,Wait方法在暂停时间会占用大量的内存,让电脑处于无响应状态。它虽然在使用上比VB中的时间控件更具方便性,但这个特点限制了它的应用范围。
(3)Wait的参数即可以使用一个指定的时间,也可以是现在之后的某个时间。例如暂停到14点钟再执行,以及从现在开始暂停14分钟,两个代码分别如下:
"14:00:00"
Now() + TimeValue("00:14:00")
(4)Wait不可以单独使用,必须配合前置对象Application。
调用内置对话框
〖案例要求〗:打开设置打印标题对话框。
〖知识要点〗:Dialogs
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 打开设置打印标题对话框()
(23).Show
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“打开设置打印标题对话框”,将会弹出图所示对话框。
图 打开内置对话框
〖语法补充〗:
(1)Dialog 对象代表内置的Excel对话框,利用Show方法可以显示绝大部分Excel的对话框。它的基本语法如下:
(Index).Show
其参数Index用于指定对话框类型,可以使用内置的常量,也可以使用数值。由于该量很多,限于篇幅所限,读得可以到随书光盘中查看。
(2)虽然通过菜单、功能区也可以打开Excel的对话框,但使用VBA方法可以将部分综合型对话框中某个选项抽离出来,或者将某一类型的选项集中一个对话框,对工作也是具有实用性的。例如编号为27和52的“显示选项”对话框和“清除”对话框,见图和图所示:
图 显示选项 图 清除
(3)如果用户需要逐个显示所有Dialogs支持的所有对话框,可以使用以下过程:
Sub 显示内置对话框()
On Error Resume Next
Dim Count As Integer, Item As Integer
For Item = 1 To
(Item).Show
Next
End Sub
滚动显示Excel状态栏信息
〖案例要求〗:在状态栏滚动显示几句歌词。
〖知识要点〗:StatusBar
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Dim Bl As Boolean
Sub 诗词滚动()
Dim Tim As Integer
Bl = True '将变量设置为true
'待显示的诗歌
= "相见时难别亦难 东风无力百花残 春蚕到死丝方尽 蜡炬成灰泪始干 "
Do
For Tim = 1 To 2500 '这个数字根据需要修改
DoEvents '转移控制权,从而突出动画效果
Next Tim
'交换文字位置,形成动画
= Mid(, 2, Len() - 1) & Left(, 1)
Loop Until Bl = False '直到Bl变量成为False时停止循环
= ""
End Sub
Sub 停()
Bl = False '将变量设为False
End Sub
其中“Dim Bl As Boolean”声明公共变量必须放在模块的顶部,不能在两个过程之间。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“诗词滚动”,在状态栏立即出现一段歌词在滚动。根据大家的硬件不同,其滚动的速度也有差异。
〖语法补充〗:
(1) 属性可返回或设置状态栏中的文字。可以对它直接赋值,但因状态栏空间有限,汉字字符不宜超过30个。
(2)状态栏信息在重启Excel时会重置,如果需要永远显示这个滚动信息,可以将它加入工作簿及Open事件中。
添加自定义序列
〖案例要求〗:将加学位序列,使工事表中的应征者可以按学历排序。
〖知识要点〗:AddCustomList
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 添加序列()
'将数组中添加到序列中
Array("小学", "初中", "高中", "大学", "博士")
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加序列”,在
“自定义序列”对话框中可以看到刚才所添加的序列。
图 添加新的序列
(4)如果需要对图所示的人事资料表以B列为基准对所有人员按学历升序排序,那么可以在排序时在“排序”对话框的“次序”下拉列表中选择“自定义序列”的方式按学历排序。排序后结果如图所示:
图 人事资料表 图 排学历排序的结果
(5)如果不手工排序,而是通过VBA完成排序,那么Excel 2007可以不借助自定义序列,而使用Sort方法独立完成,而Excel 2003则必须使用排序功能配合自定义序列功能完成。
Excel 2007对图中进行按学历排序的过程如下:
Sub 自定义排序1() '2007专用
'设置排序状态及条件区域
Key:=Range("B2:B10"), SortOn:=xlSortOnValues, _
Order:=xlAscending, CustomOrder:="小学,初中,高中,大学,博士"
'对A2:B1区域按B2:B10进行自定义排序
With
.SetRange Range("A2:B10") '实际排序区域
.Apply ' 应用排序格式
End With
End 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:=xlSortNormal
End Sub
本过程为2003中使用的代码,为了体现兼容性,Excel 2007中使用此过程也可以完成按学历排序。但是微软显然意识到了这种方式的缺陷——OrderCustom只能以相对位置指定自定义序列,当对序列进行了编辑或者增删后容易产生错误——才在Excel 2007中做了改进,读者也应该与时俱进,使用更先进的排序方式。
〖语法补充〗:
(1)方法可以为自定义自动填充或自定义排序添加自定义列表。它的具体语法如下:
(ListArray, ByRow)
其第一参数用于指定字符串数组或Range对象为序列的数据源;第二参数则仅当ListArray为Range对象时使用。如果为 True,则使用区域中的每一行创建自定义列表;如果为 False,则使用区域中的每一列创建自定义列表。
(2)如果需要一组不确定的数据添加到自定义序列,或者该组数据非常大,通常从单元格导入数据,而非直接使用数组。例如需要将A、B、C……Z等等字母添加到自定义序列,那么可以用单元格做辅助区,代码如下:
Sub 定义字母序列()
Dim i As Integer
'防错,否则已经有该序列时将出错
On Error Resume Next
'关闭屏幕刷新,加愉速度
= False
For i = 1 To 26 '遍历26个字母
'利用Chr函数产生字符码,代码65即可大写字母A,66即为B,以此类推
Cells(, i) = Chr(64 + i)
Next i
'将辅助区导入序列
Cells(, 1).Resize(1, 26)
'删除辅助区
Cells(, 1).Resize(1, 26).Clear
= True
End Sub
添加名称
〖案例要求〗:生成包括一周中每天的名称,方便单元格输入。
〖知识要点〗:Names
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 生成日期名称()
'添加一个名称,包括一个星期每一天
Name:="星期", RefersToR1C1:="={""星期一"",""星期二"",""星期三"",""星期四"",""星期五"",""星期六"",""星期日""}"
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成日期名称”,将会产生一个工簿级别的名称;
(4)选择A1单元格,并输入公式“=INDEX(星期,ROW(A1))”,将公式向下填充至A7,直充结果如图所示,这是常量名称的应用;
(5)也可以利用区域数组公式一次性输入到区域中。鉴于代码中构造数组时使用了逗号,表示横向数组,那么输入公式需要进行转置。完整公式为“=TRANSPOSE(星期)”,效果见图所示:
图 逐个取出名称中的元素 图 一次性取出数组所有元素
〖语法补充〗:
(1) 方法可以定义新名称。其语法如下:
表达式.(Name, RefersTo, Visible, MacroType, ShortcutKey, Category, NameLocal, RefersToLocal, CategoryLocal, RefersToR1C1, RefersToR1C1Local)
其中表达式可以是Application、Workbook和Worksheet等,而其实等效于。
其中最常用的是前两个参数,用于指定名称的引用地址和名字。
(2)Names有工作表级别和工作簿级别之分,而和方法都添加的工作簿级别名称;方式添加的是工作表级别名称,只能指定的工作表才可以使用该名称。
将自定义数标记为易失性函数。
〖案例要求〗:开发一个自定义函数,获取选区的地址。有相对引用、绝对引用和混合引用可选,当单元格插入或者删除时要及时更新公式的值。
〖知识要点〗:Volatile、ThisCell
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Function Address2(style As Byte, Optional rng As Range) '声明函数名称,第二个为可选参数
'声明为易失性函数
'如果省略第二参数则将公式所在单元格赋与变量
IF rng Is Nothing Then Set rng =
'根据第一参数决定单元格地址的引用形式,包括相对引用、绝对引用、混合引用
Select Case style
Case 1
Address2 =
Case 2
Address2 = (0, 0)
Case 3
Address2 = (1, 0)
Case Else
Address2 = (0, 1)
End Select
End 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)方法用于将用户自定义函数标记为易失性函数,无论何时在工作表的任意单元格中进行计算时,易失性函数都必须重新进行计算。
(2)属性返回一个单元格,用户定义的函数将作为Range对象从这里调用。更通俗地说法即ThisCel代表公式所在单元格。
选定任意工作簿中的任意区域。
〖案例要求〗:选择第三个工作表的已用区域
〖知识要点〗:Goto
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 选择其它表已用区域()
Reference:=Worksheets("Names").UsedRange, scroll:=True
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“选择其它表已用区域”,程序将选择名为“Names”的工作表的已用区域。
〖语法补充〗:
(1)方法可以选定任意工作簿中的任意区域,并且如果该工作簿未处于活动状态,就激活该工作簿。它的语法如下:
(Reference, Scroll)
其中第一参数表示待选定的目标,如果省略该参数,目标将为最近一次用 Goto 方法选定的区域。第二参数如果为 True,则滚动窗口直至区域的左上角出现在窗口的左上角中。如果为 False,则不滚动窗口。
(2)Goto方法与Range的Select方法有以下区别:
表11-8 Goto方法与Select方法的区别
比较项目
GOTO
SELECT
如果指定的区域不在位于最前面屏幕的工作表中,选定该区域之前切换至该工作表
是
否
具有让用户滚动目标窗口的 Scroll 参数
是
否
前一次选定区域被增加到以前选定区域的数组(PreviousSelections)中
是
否
具有 Replace 参数
否
是
跨表或跨工作簿时可以一步完成
是
否
(3)如果本例中的任何交Select来完行,那么必使用两步,先激活工作表,再选择单元格。完整代码如下:
Sub 选择其它表已用区域()
Worksheets("Names").Select
Worksheets("Names").
End Sub
设置应用程序的可见性。
〖案例要求〗:为Excel设定登录权限,在录入用户名时隐藏应用程序,正确后再恢复
〖知识要点〗:Visible
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 验证密码()
'隐藏Excel应用程序
= False
Dim Users As String
'让用户确认自己的权限
Users = ("请输入密码", "权限确认", , , , , , 3)
= False
'如果不是录入Admin则退出程序
IF Users <> "Admin" Then
End IF
'恢复Excel程序的可见性
= True
= True
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“验证密码”,将弹出图所示权限验证对话框,同时隐藏Excel工作簿的主体界面。如果输入任何不等于“Admin”的字符串,Excel将关闭,否则返回正常页面。
图 让用户录入密码
实际工作中需要将过程改成工作簿Open事件更好,让其自启动。
〖语法补充〗:
(1) 属性用于返回或设置一个Boolean值,它确定Excel应用程序对象是否可见。
(2)在隐藏主体界面后,必须在适当的时候恢复,否则无法继续操作Excel.
设置批注的显示方式
〖案例要求〗:弹出对话框,根据用户的录入数值设置批注显示方式
〖知识要点〗:DisplayCommentIndicator
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 设置批注显示方式()
Dim Style As Byte
On Error Resume Next '防错
Star: '设置一个标签
'让用户选择批注显方式
Style = ("请确认批注显示方式:" & Chr(10) & "输入1:显示批注与红色箭头" & Chr(10) _
& "输入2:仅仅显示红色箭头" & Chr(10) & "输入3:隐藏批注与箭头", "显示方式", 2, , , , , 1)
'如果有错误(超过Byte的允许范围),那么返回Star标签继续执行
IF > 0 Then : GoTo Star
'如果在1到3范围之外则返回Star标签继续执行
IF Style < 1 Or Style > 3 Then MsgBox "只能是1、2或者3", 64, "提示": GoTo Star
Select Case Style '根据用户的录入数值设置批注的显示方式
Case 1
= xlCommentAndIndicator '显示全部
Case 2
= xlCommentIndicatorOnly '仅显示箭头
Case 3
= xlNoIndicator '全部隐藏
End Select
End Sub
以上过程中,利用了防错机制处理不规范的录入值。同时对于不在允许范围1、2和3之内的输入值,也会返回标签Star处继续等待用户录入新的值,一直循环到出现规范数值为止。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“设置批注显示方式”,将弹出“显示方式”对话框,有三个设置选项,默认值为2,见图所示。如果输入负数或者大于256的任何数值都会继续弹出对话框等待用户录入正确值;如果用户输入4,则会弹出图所示的提示,然后再返回对话框等待用户录入正确值;
图 让用户输入决定显示方式的值 图 输入4时弹出的提示
(4)如果分别输入1、2或者3,那么其结果分别如图、图和图所示:
图 显示批注与箭头 图 显示箭头 图 全部隐藏
〖语法补充〗:
(1)属性可返回或设置单元格显示批注和标识符的方式。可以是XlCommentDisplayMode常量之一。XlCommentDisplayMode常量包括以下三个可选项:
表11-9 显示批注的三种常量
名称
值
描述
xlCommentAndIndicator
1
任何时候都显示批注和标识符
xlCommentIndicatorOnly
-1
只显示标识符。鼠标指针在单元格上移动时显示批注
xlNoIndicator
0
任何时候都不显示批注也不显示标识符
(2)本例三个方式的设置对当前工作簿中所有工作表都生效。
(3)DisplayCommentIndicator不能单独使用,需要配合前置对象Application。
Range对象应用案例
Range对象是数据最基本的载体,制表中使用最频繁的自然就是单元格对象——Range。本节对Range对象进行实例演示。
清除单元格格式
〖案例要求〗:图所示单元格的显示值并非单元格的实际值,解决这个问题即为对表格清除格式设置。现要求除区域中数值单元格的格式。
〖知识要点〗:ClearFormats、SpecialCells
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 清除数值区格式()
Range([a1], Cells(, 4).End(xlUp)).SpecialCells(xlCellTypeConstants, 1).ClearFormats
End Sub
以上过程中使用“End(xlUp)”主要为了提升程序的通用性,当工作表中删除或者增加数据时程序可以不用修改代码也可以正确操作所有数据。而使用“SpecialCells”方法则是为了定位数值、忽略其它数据。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“清除数值区格式”,程序执行完毕后,所有数字单元格恢复其原来的值,见图所示:
图 成绩表 图 恢复成绩表原始数据
〖语法补充〗:
(1)方法用于返回一个Range 对象,该对象代表与指定类型和值匹配的所有单元格。类似于菜单中的“定位”工具。SpecialCells有以下参数可选,指定单元格类型:
表11-10 SpecialCells参数列表
常量
含义
值
xlCellTypeAllFormatConditions
任意格式单元格
-4172
xlCellTypeAllValidation
含有验证条件的单元格
-4174
xlCellTypeBlanks
空单元格
4
xlCellTypeComments
含有注释的单元格
-4144
xlCellTypeConstants
含有常量的单元格
2
xlCellTypeFormulas
含有公式的单元格
-4123
xlCellTypeLastCell
已用区域中的最后一个单元格
11
xlCellTypeSameFormatConditions
含有相同格式的单元格
-4173
xlCellTypeSameValidation
含有相同验证条件的单元格
-4175
xlCellTypeVisible
所有可见单元格
12
如果使用了XlSpecialCellsValue参数,那么它还可以使用以下常量,用于指定值的类型:
表11-11 XlSpecialCellsValue常量表
常量
值
xlErrors
16
xlLogical
4
xlNumbers
1
xlTextValues
2
在本例中SpecialCells(xlCellTypeConstants, 1)表示定位于常量中的数值。
(2)方法用于清除对象的格式设置。格式设置包括颜色、字体名称、字体大小、边框、数字格式、对齐方式等等属性,但不包括单元格中的数据。如果需要删除数据保留格式,那么可用以下两种方式:
——清除单元格内容
Range= "" ——赋空值
本节关于Range对象的所有实例代码参见光盘:..\ 第十一章\
复制单元格数据
〖案例要求〗:将指定工作表中已用区域的数据复制到当前表,如果当前表空白,则在A1开始放置数据,否则放置在已用区域的下一行开始放置数据。
〖知识要点〗:Copy、PasteSpecial
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 将其它工作表的已用区域复制到本工作表()
Dim rng As Range
'如果A列空白
IF ([a:a]) = 0 Then
Set rng = [a1] '将A1赋与变量Rng
Else
'否则将第一个空单元格赋与变量Rng
Set rng = Cells(, 1).End(xlUp).Offset(1, 0)
End IF
'将第一个工作表中的单元格复制到Rng(如果复制的数据多于一个单元格,则以Rng为基准向右、向下延伸)
Sheets("ClearFormats"). rng
End Sub
以上过程首先判断当前表A列是否空白,如果空白则将数据复制到A1,否则复制到已用区域的下一个空行。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将其它工作表的已用区域复制到本工作表”,工作表“ClearFormats”的所有数据都会复制到当前表,包括格式信息。
〖语法补充〗:
(1)方法用于将单元格区域复制到指定的区域或剪贴板中。它的语法如下:
(Destination)
参数Destination用于指定数据要复制到的新区域。如果省略此参数, Excel会将数据复制到剪贴板。例如将A1的值复制到B3,那么使用以下语句:
[a1].Copy [b3]
如果将A1的值复制到剪贴版供后续调用,那么可以使用以下语速句:
[a1].Copy
(2)方法总是带有区域中的格式信息,而不是仅仅复制数据。
(3)复制一个区域时,Copy的第二参数只需要指定一个单元格,粘贴数据时将以该单元格为基准向右、向下扩展到与原数据区域中同大小的区域。如果数据源区域是整行或者整列,那么这个目标单元格必须位于工作表的左端或者顶端,否则因空间不足而产生运行时错误。
(4)如果需要复制区域中的某项信息,例如数值、格式或者数据有效性等等,那么应该使用选择性粘贴。选择性粘贴必须通过两步才能完成,即复制数据到剪贴版,最后粘贴数值或者格式信息。如果本例中复制区域改为复制数值,那么代码可以修改为:
Sub 复制数值()
'复制目标数据
Sheets("ClearFormats").
Dim rng As Range
IF ([a:a]) = 0 Then
Set rng = [a1]
Else
Set rng = Cells(, 1).End(xlUp).Offset(1, 0)
End IF
'在当前表A列第一个非空单元格选择性粘贴值
Paste:=xlPasteValues, Operation:=xlNone
End Sub
(5)方法用于将Range从剪贴板粘贴到指定的区域中。它的语法如下:
(Paste, Operation, SkipBlanks, Transpose)
其中四个参数的含义见表11-12:
表11-12 参数详解
名称
数据类型
描述
Paste
XlPasteType
要粘贴的区域部分
Operation
XlPasteSpecialOperation
粘贴操作
SkipBlanks
Variant
如果为 True,则不将剪贴板上区域中的空白单元格粘贴到目标区域
Transpose
Variant
如果为 True,则在粘贴区域时转置行和列。默认值为 False
其中第一参数数据类型XlPasteType有11个可选项,包括值、格式、公式、有效性等等。详见表11-14所示:
表11-13 选择性站贴的可选数据类型
名称
值
描述
xlPasteValues
-4163
粘贴值
xlPasteComments
-4144
粘贴批注
xlPasteFormulas
-4123
粘贴公式
xlPasteFormats
-4122
粘贴复制的源格式
xlPasteAll
-4104
粘贴全部内容
xlPasteValidation
6
粘贴有效性
xlPasteAllExceptBorders
7
粘贴除边框外的全部内容
xlPasteColumnWidths
8
粘贴复制的列宽
xlPasteFormulasAndNumberFormats
11
粘贴公式和数字格式
xlPasteValuesAndNumberFormats
12
粘贴值和数字格式
xlPasteAllUsingSourceTheme
13
使用源主题粘贴全部内容
根据上表,如果仅仅复制格式,那么复制A1的有效性设置与列宽到C1,即将C1单元格的宽度与A1一致,且让其数据有效性设置也一致,但不改变C1的原有数据,那么可以采用以下过程:
Sub 复制A1的有效性设置与列宽()
[A1].Copy
[C1].PasteSpecial Paste:=8
[C1].PasteSpecial Paste:=6
End Sub
将区域中的数据合并到一个单元格中
〖案例要求〗:将区个区域合并居中,同时保留合并前的所有数据。
〖知识要点〗:Merge、UnMerge、HorizontalAlignment、VerticalAlignment
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 合并指定区域的值()
Dim rng As Range, str As String
With Range("A1:C1")
'如果区域中有合并单元格,则先取消其合并
.UnMerge
For Each rng In Range("A1:C1")
'遍历区域,合并所有数据
str = str &
Next rng
'关闭警告窗口
= False
.Merge '合并区域
= True
'居中对齐,并将合并后的值赋与合并区域
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.Value = str
End With
End Sub
以上过程将指定区域取消合并(如果存在合并区域的话),然后连接区域中的所有数据,再将区域合并居中,最后将连接的字符串赋与合并区域。
合并区域时会提示
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并指定区域的值”。如果工作表中A1:C1有图所示的数据,那么执行过程后将产生图所示效果——合并单元格的同时将数据也合并。
图 合并前的日期 图 合并后的日期
〖语法补充〗:
(1)方法由指定的Range对象创建合并单元格。其语法如下:
(Across)
其中参数Across如果为 True,则将指定区域中每一行的单元格合并为一个单独的合并单元格。默认值是 False。例如将A1:D4合并为4个合并区域,每行一个区域,那么可以使用以下代码,对应于Excel 2007功能区【开始】\【合并后居中】\【跨越合并】,执行结果见图所示:
Range("A1:D4").Merge (True)
图 利用代码实现跨越合并
(2) 方法用于将合并区域分解为独立的单元格。在取消合并区域后,仅仅该区域左上角单元格有数据,其它单元格保持空白。
(3) 属性和VerticalAlignment属性代表单元格的对齐方式,包括以下五种方式:
xlCenter
xlDistributed
xlJustify
xlLeft
xlRight
多工作表数据合并且添加边框
〖案例要求〗:合并某班三个年级的成绩,存放在当前表D1。
〖知识要点〗:Consolidate、Borders
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 合并计算()
'将多工作表的成绩数据合并,保存区域与合并区域大小一致,但其左上角单元格是D1
[D1].Consolidate Sources:=Array("一班!R1C1:R8C2", "二班!R1C1:R8C2", "三班!R1C1:R8C2"), _
Function:=xlSum, TopRow:=True, LeftColumn:=True, CreateLinks:=False
'对合并成绩区域添加边框
[D1]. = xlContinuous
End Sub
以上过程将三个工作表的相同大小的数据区域进行合并计算,然后添加边框。汇总方式是求和。根据需求,也可以对三个年级的成绩进行平均、计数等等计算。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并计算”,三个班级的成绩将合并到当前表中,存放区域是D1为基准,扩展到合并前区域的相同大小。图中包括某班三个年级的数据及合并后的汇总值。
图 合并三个工作表的数据
〖语法补充〗:
(1)方法可以将多个工作表中多个区域的数据合并计算至单个工作表上的单个区域中。它的具体语法如下:
Range..Consolidate(Sources, Function, TopRow, LeftColumn, CreateLinks)
五个参数的含义如下:
表11-14 方法参数详解
名称
描述
Sources
以文本引用字符串数组的形式给出合并计算的源,该数组采用 R1C1-样式表示法。这些引用必须包含将要合并计算的工作表的完整路径
Function
XlConsolidationFunction 常量之一,用于指定合并计算的类型。
TopRow
如果为 True,则基于合并计算区域中首行内的列标题对数据进行合并。如果为 False,则按位置进行合并计算。默认值为 False
LeftColumn
如果为 True 则基于合并计算区域中左列内的行标题对数据进行合并计算。如果为 False,则按位置进行合并计算。默认值为 False
CreateLinks
如果为 True,则让合并计算使用工作表链接。如果为 False,则让合并计算复制数据
(2)其中第二参数表示汇总方式,包括以下几个可选常量:
表11-15 汇总方式详解
名称
值
描述
xlAverage
-4106
平均
xlCount
-4112
计数
xlCountNums
-4113
只计数数值
xlMax
-4136
最大值
xlMin
-4139
最小值
xlProduct
-4149
乘
xlStDev
-4155
基于样本的标准偏差
xlStDevP
-4156
基于全体数据的标准偏差
xlSum
-4157
总计
xlUnknown
1000
未指定任何分类汇总函数
xlVar
-4164
基于样本的方差
xlVarP
-4165
基于全体数据的方差
(3)合并计算是一项非常有用的工具,但属于Excel默认功能以外的加载宏工具,需要安装才可以使用。
(4)对象由四个Border对象组成的集合,它们代表单元格的四个边框。利用其LineStyle 属性可以对单元格设置四个方向的边框线。表11-16包括所有可选的线型:
表11-16 边框边线列表
名称
值
描述
xlContinuous
1
实线
xlDash
-4115
虚线
xlDashDot
4
点划相间线
xlDashDotDot
5
划线后跟两个点
xlDot
-4118
点式线
xlDouble
-4119
双线
xlLineStyleNone
-4142
无线条
xlSlantDashDot
13
倾斜的划线
让高度与宽度自动适应数据
〖案例要求〗:对当前工作簿中所有工作表进行调整,让其根据单元格的字符敲开整行高与列宽。
〖知识要点〗:AutoFit
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 自动适应()
Dim sht As Worksheet
'遍历所有工作表
For Each sht In Sheets
'让已用区域的行高与列宽都自动适应文字
Next sht
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“自动适应”,所有工作表中的单元格都将瞬间调整完成。
〖语法补充〗:
(1)方法用于更改区域中的列宽或行高以达到最佳匹配。Range对象必须是行或行区域,或者列与列区域。否则该方法将产生错误。本例中采用了Rows和Columns来将已用区域转换成行区域与列区域。
(2)如果需要将单元格B2的当前区域调整为自动行高,可以使用以下代码:
Range("B2").
(3)如果需要在输入字符时就自动整列,而不是手工执行程序,那么可以将代码置于工作表的Change事件代码。
在区域中精确查找
〖案例要求〗:在当前工作表中精确查找名字为两个,且第一个字是“天”的公司。
〖知识要点〗:Find
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 精确查找()
Dim 目标 As Range, rng As Range, firstAddress As String
'对当前表已用区域中查找
With
'指定查找方式为按值找查,对象是“天?公司”,?表示占位符
'LookAt:=xlPart表示模糊查找,LookAt:=xlWhole表示精确查找
Set 目标 = .Find("天?公司", LookIn:=xlValues, LookAt:=xlWhole)
IF Not 目标 Is Nothing Then '如果找到
firstAddress = 目标.Address '记录其记录
Do '开始循环,查找下一个
'此IF语句用于将查到的所有目标合并成一个区域
IF rng Is Nothing Then
Set rng = 目标
Else
Set rng = Union(目标, rng)
End IF
Set 目标 = .FindNext(目标) '查找下一个
'循环查找,只要下一次查到单元格地址不等于第一次查到目标的单元格就继续查找
Loop While Not 目标 Is Nothing And 目标.Address <> firstAddress
End IF
End With
'选择所有找到的结果
End Sub
该过程在当前表已用区域中查找“天?公司”,并将找到的所有单元格合并成一个区域,最后选择该区域。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“精确查找”,程序会瞬间选择所有符合条件的单元格,见图所示(为了更清楚的看到选择的对象,特将选区填充不同的背景色加以区分);
(4)如果使用模糊查找,找到所有包括“天”字的公司,那么代码中“天?公司”需要修改成“*天*”,其查找结果见图所示:
图 查找“天?公司” 图 查找“*天*”公司
〖语法补充〗:
(1)方法用于在区域中查找特定信息,包括精确查找与模糊查找。它的语法如下:
(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
各参数含义如下:
表11-17 方法各参数含义列表
名称
描述
What
要搜索的数据,可为字符串或任意 Excel 数据类型
After
表示搜索过程,将从其之后开始进行的单元格此单元格对应于从用户界面搜索时的活动单元格的位置
LookIn
信息类型
LookAt
可为以下 XlLookAt 常量之一:xlWhole 或 xlPart
SearchOrder
可为以下 XlSearchOrder 常量之一:xlByRows 或 xlByColumns
SearchDirection
搜索的方向
MatchCase
如果为 True,则搜索区分大小写默认值为 False
MatchByte
只在已经选择或安装了双字节语言支持时适用如果为 True,则双字节字符只与双字节字符匹配如果为 False,则双字节字符可与其对等的单字节字符匹配
SearchFormat
搜索的格式
其中有几个参数较为重要:
第一参数What:它表示要查找的对象,支持通配符“?”和“*”。同时因为支持通配符,使其具有了控制精确查找与模糊查找的功能。
第三参数LookIn:它用于指定查找方式,包括按值查找、按公式查找及按批注查找,在本例中是按值查找。按值查找与按公式查找的区别是:按公式查找时,是在单元格显示在编辑栏中的字符中查找,而按值查找时,则在单元格的显示字符中查找,这两者有较大的区别。例如某单元格中的公式是“=LEFT("中国电信",3)”,如果按值查找“中国电信”,那么它不符合条件,如果按公式查找则符合条件。
第四参数LookAt:它可以指定是精确查找或者模糊查找,它值为xlWhole时表示精确查找,值为xlPart时为模糊查找。所谓精确查找,是指单元格整体符合条件;而模糊查找则表示单元格部分字符符合件即可。例如单元格中字符串为“中国电信”,那么查找“中国”时,只有模糊查找时它才符合条件。
但是因为第一参数支持通配符,那么将LookAt设置为xlWhole时仍然可以实现糊模查找。例如第一参数为“*中国*”,那么不管LookAt参数如何设置都按模糊查找处理。
第七参数MatchCase:它表示查找英文时是否区分大小写,设为True时区分大小写,设置为False时不区公大小写。
(2)Find方法默认返回一个Range对象,它代表第一个在其中找到该信息的单元格。不管有多少符合条件的单元格,只返回一个单元格。如果要查找其它值,需要配合FindNext继续查找下一个。
(3)Find 方法不影响选定区域或当前活动的单元格,它仅仅将符合条件的单元格返回给VBA过程,但不选择或者激活目标单元格,程序员需要在代码中加入Select才可以选择目标。
(4)每次使用此方法后,参数 LookIn、LookAt、SearchOrder 和 MatchByte 的设置都将被保存。如果用户未在代码中注明这四个参数,那么则调用上次保存的值。所以为了不产生意外,程序员应该养成注明参数的习惯,特别是LookIn和LookAt两个重点参数。
(5)Find查找支持通配符,具有很强的查找能力。但是不支持比较运算符,例如查找小于20的值,或者500到100之间的值。如果有这种需求,需要借用For循环来完成。
替换不规则货品名称
〖案例要求〗:将当前工作表产品名中的“计算机”为“电脑”。
〖知识要点〗:Replace
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 替换计算机为电脑()
'将当前表所有“计算机”替换为“电脑”
What:="计算机", Replacement:="电脑", LookAt:=xlWhole
End Sub
以上过程中使用UsedRange表示对当前工作表所有区域进行替换。如果仅仅对A列对行列替换,则可以将UsedRange修改为[A:A]。在使用上替换比查找简单很多,速度也更快。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“替换计算机为电脑”,则当前表所有“计算机”会被替换为“电脑”。但是“银河计算机”这类单元格则会忽略,因为代码中LookAt已设置为精确查找。
〖语法补充〗:
(1)方法返回Boolean,它表示指定区域内单元格中的字符。它的语法如下:
(What, Replacement, LookAt, SearchOrder, MatchCase, MatchByte, SearchFormat, ReplaceFormat)
其中各参数含义如下:
表11-18 方法含义详解
名称
描述
What
Microsoft Excel 要搜索的字符串
Replacement
替换字符串
LookAt
可为以下 XlLookAt 常量之一:xlWhole 或 xlPart
SearchOrder
可为以下 XlSearchOrder 常量之一:xlByRows 或 xlByColumns
MatchCase
如果为 True,则搜索区分大小写
MatchByte
只有在Excel中选择或安装了双字节语言时,才能使用此参数。如果为True,则双字节字符只与双字节字符匹配。如果为False,则双字节字符可与其对等的单字节字符匹配
SearchFormat
该方法的搜索格式
ReplaceFormat
该方法的替换格式
(2)Excel的自动更正方法可以实现替换功能,但它与Replace方法也有本质上的区别。Replace方法可以对任意区域实现替换,而自动更正无法仅对某个区域发生作用,而是作用于整个工作簿。
将公式添加到公式
〖案例要求〗:将所有具有公式的单元格添加公式批注,批注内容为该单元格的公式。
〖知识要点〗:AddComment
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 将公式添加到批注()
Dim rng As Range
= False
On Error Resume Next
'遍历所有公式单元格
For Each rng In (xlCellTypeFormulas, 23)
'如果存在错误(当前表没有公式)则退出程序
IF Err <> 0 Then GoTo endd
'首先清除批注(假设有的话)
'添加批注,批注的文字等于该单元格的公式
With ()
.Visible = True '显示批注
. '选择批注外框
= True '大小自动适应文字
.Visible = False '隐藏批注
End With
Next rng
endd:
= True
End Sub
以上过程仅对公式所在单元格添加批注,那么工作表不存在公式时则会出错。为了防错弹出错误对话框,过程中使用了防错机制。在进行循环的第一个阶段发现有错误时就可以退出程序,从而防止弹出对话框,同时避免不必要的循环,节约时间。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将公式添加到批注”,工作表中抽所有带有公式的单元格将瞬间完成公式到批注,效果见图所示。
图 将公式加入批注
〖语法补充〗:
(1) 方法可为区域添加批注。其语法如下:
表达式.AddComment(Text)
其中Text参数表示添加批注的内容,可以借用Chr(10)实现批注换行。
(2)如果单元格中已经有批注,那么对工作表添加批注会产生错误。而对没有批注的单元格删除批注却不会产生错误,所以应养成添加批注之前总是删除一次原有(不管有没有)批注。
(3)对批注设置“自动调整大小”时,必须在显示批注的情况下完成,所以使用“AutoSize = True”之前必须显示批注,且选择批注图形;而在之后对它进行隐藏。
填充工作日
〖案例要求〗:将A2的日期向下填充到月底,忽略周末。
〖知识要点〗:DataSeries、Resize
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 填充日期()
Dim dat As Long
'首先计算出本月最后一天的日期序列
dat = ([a2], 0)
'将A2的值向下填充,按工作日、步长为1的方式填充到月底为止
[a2].Resize(dat - [a2], 1).DataSeries Rowcol:=xlColumns, Type:=xlChronological, Date:= _
xlWeekday, Step:=1, Stop:=dat, Trend:=False
End Sub
以上过程中重点在于计算A2的日期当月最后一天的日期,重置区域和设置终止值都这个日期值为基准。代码中借用了工作表函数EoMonth来完成,该函数是Excel 2007自带的函数,而Excel 2003中需要安装分析工具库才可以使用该函数。
DataSeries方法填充日期时,使用xlWeekday做为参数,表示仅仅按工作日计算,忽略周六与周日的日期
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“填充日期”,单元格A2的日期填充前面的效果见图和图所示。不管A2的值是哪一天的日期,程序总是按该月的实际天数向下填充到月尾。
图 原始日期 图 按工作日填充后的日期
〖语法补充〗:
(1)方法用于在指定区域内创建数据系列。它的具体语法如下:
(Rowcol, Type, Date, Step, Stop, Trend)
其中各参数含义见下表所示:
表11-19 方法参数含义详解
名称
数据类型
描述
Rowcol
Variant
可以是 xlRows 或 xlColumns 常量,分别表示按行或列输入数据系列。如果省略本参数,则使用区域的大小和形状
Type
XlDataSeriesType
数据序列的类型
Date
XlDataSeriesDate
如果 Type 参数为 xlChronological,则 Date 参数指示单步执行日期单位
Step
Variant
系列的步长值。默认值为 1
Stop
Variant
系列的终止值。如果省略本参数, Excel将填满整个区域
Trend
Variant
如果为 True,则创建一个线性趋势或增长趋势。如果为 False,则创建一个标准数据序列。默认值为 False
其中第二参数Type用于设置填充序列的类型,有以下四种可选项:
表11-20 方法之XlDataSeriesType数据类型常量
名称
值
描述
xlAutoFill
4
按照“自动填充”设置对系列进行填充
xlChronological
3
用数据值进行填充
xlDataSeriesLinear
-4132
扩展值,假定一个加法级数(例如,“1, 2”被扩展为“3, 4, 5”)
xlGrowth
2
扩展值,假定一个乘法级数(例如,“1, 2”被扩展为“4, 8, 16”)
本例是日期序列填充,通过第三参数可以控制日期的计算方式,它有以下四种算法:
表11-21 方法之日期常量
名称
值
描述
xlDay
1
日
xlMonth
3
月
xlWeekday
2
工作日
xlYear
4
年
(2)属性用于调整指定区域的大小,返回Range对象调整后的区域。其语法如下:
(RowSize, ColumnSize)
在本例中,第一参数使用月尾的日期值减去A2的日期值,第一参数使用1,则可以产生一个多行一列的新区域。
对区域添加四周边框
〖案例要求〗:对区域添加上下左右边框,忽略内部线框。
〖知识要点〗:BorderAround
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 添加四周红色粗边框()
'如果选择了单元地格,则添加红色粗边框
IF TypeName(Selection) = "Range" Then
ColorIndex:=3, Weight:=xlThick
End IF
End Sub
以上过程中对选择的对象进行判断,如果是单元格对象才添加边框。当然也可以利用“On Error Resume Next”来防错,可以产生同样的效果。
(3)返回工作表,选择待添加边框的区域“B2:E10”,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加四周红色粗边框”。程序执行前后的单元格效果分别见图和图所示。
图 添加边框前 图 添加边框后
〖语法补充〗:
(1)方法可以向单元格区域添加边框,并设置该新边框的Color、LineStyle和Weight属性。具体语法如下:
(LineStyle, Weight, ColorIndex, Color)
其中四个参数的含义如下:
表11-22 参数详解
名称
数据类型
描述
LineStyle
Variant
用于指定边框线样式的 XlLineStyle 常量之一
Weight
XlBorderWeight
边框粗细
ColorIndex
XlColorIndex
边框颜色,作为当前调色板的索引或作为XlColorIndex常量
Color
Variant
边框颜色,以 RGB 值表示
其中第二参数用于指定边框的粗细程序,有以下四种可选项:
表11-23 代表边框粗细的常量
名称
值
描述
xlHairline
1
细线(最细的边框)
xlThin
2
细
xlThick
4
粗(最宽的边框)
xlMedium
-4138
中等
而第三参数用于指定边框颜色地址,可以使用1到56之间的数值,表示添加彩色边框。如果将ColorIndex 参数设置为xlColorIndexNone,表示无色,那么将采用默认的黑色边框。
(2)BorderAround是添加四周的边框,而Borders在功能上要强大一些,可以添加四周任意一边的边框,或者内部的边框。所以他们在应用范围上有所区别。
多区域合并
〖案例要求〗:将选择的区域合并,如果同时选择多个区域,就合并多个区域,且合并区域时保留区域中所有字符。
〖知识要点〗:Areas
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 多区域合并()
Dim i As Byte, Item As Integer, Str As String
'判断用户选择的对象是否单元格
IF TypeName(Selection) = "Range" Then
'关闭警告框
= False
'遍历所有区域
For i = 1 To
'将区域中所有字符串起来,用空格隔开
For Item = 1 To (i).Count
IF Len((i)(Item)) > 0 Then
Str = Str & (i)(Item) & " "
End IF
Next Item
'将一个区域合并,并字符串赋与合并后的区域
(i).Merge
(i) = Str
Str = "" '将变量重置为空
Next i
= True
End IF
End Sub
以上过程中为了防错,首先判断选择对象的类型,如果非单元格对象就出程序。然后关掉Excel的警告框,再对区域进行逐个合并。合并后的新区域保留了合并前的所有数据,中间用空格分隔。
(3)返回工作表,假设工作表中有图所示数据,按住Ctrl键同时选择三个区域A2:C2、A4:B4和A6:C6,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“多区域合并”,三个区域合并后的效果见图所示。
图 选择三个区域 图 合并后的效果
〖语法补充〗:
(1)Areas代表由选定区域内的多个子区域或连续单元格块组成的集合。本例中首先统计区域的个数,然后通过Areas(index)方式访问每个子集。
对小于60的成绩加虚框
〖案例要求〗:将成绩表中所有不及格的成绩添加虚框。
〖知识要点〗:Borders
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 对小于60的成绩加虚框 ()
Dim rng As Range, rg As Range
'遍历成绩区域
For Each rng In Range("A1:D9")
'如果符合条件
IF rng < 60 Then
IF rg Is Nothing Then
Set rg = rng
Else
Set rg = Union(rg, rng) '合并找到的所有单元格
End IF
End IF
Next
'对合并区域添加边框
With
.LineStyle = xlDash '线型为虚线
.ColorIndex = xlAutomatic
.Weight = xlMedium '中等宽度
End With
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“对小于60的成绩加虚框”,执行过程前后,单元格状态对比如下:
图 添加虚框前 图 添加虚框后
〖语法补充〗:
(1)属性返回一个Borders集合,它代表样式或单元格区域的边框。对单元格添加框线的具体语法如下:
(XlBordersIndex).LineStyle=XlLineStyle
其中参数XlBordersIndex表示边框位置,例如上部、底部等等,如果忽略参数则表示包括所有方向的田字型边框。可选的常量及其含义见下表:
表11-24 可用的XlBordersIndex常量列表
名称
值
描述
xlDiagonalDown
5
从区域中每个单元格的左上角至右下角的边框
xlDiagonalUp
6
从区域中每个单元格的左下角至右上角的边框
xlEdgeBottom
9
区域底部的边框
xlEdgeLeft
7
区域左边的边框
xlEdgeRight
10
区域右边的边框
xlEdgeTop
8
区域顶部的边框
xlInsideHorizontal
12
区域中所有单元格的水平边框(区域以外的边框除外)
xlInsideVertical
11
区域中所有单元格的垂直边框(区域以外的边框除外)
而参数中的LineStyle 表示线型,它有以下可选的常量名称,用于指定线型:
表11-25 线型列表
名称
值
描述
xlLineStyleNone
-4142
无线条
xlDouble
-4119
双线
xlDot
-4118
点式线
xlDash
-4115
虚线
xlContinuous
1
实线
xlDashDot
4
点划相间线
xlDashDotDot
5
划线后跟两个点
xlSlantDashDot
13
倾斜的划线
(2)如果仅仅需要对一个区域的四周添加边框,利用Borders可以完成,但却没有BorderAround方便。
反向选择单元格
〖案例要求〗:选择当前工作表中已用区域的反向区域,即选择未被选中的区域。
〖知识要点〗:Select、Address
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 反向选择()
'如果工作表已保护,则退出程序
IF Then Exit Sub
'如果选择对方不是单元格则退出程序
IF TypeName(Selection) <> "Range" Then Exit Sub
= False '关闭删除工作表时的提示
= False '加快速度
Dim raddress As String, taddress As String
'如果已全选则退出程序
IF = And = Then End
'如果已用区域与选区不存在交集或者选区等于已用区域,那么就选择已用区域
IF Intersect(, Selection) Is Nothing Or = Then
: Exit Sub
End IF
raddress = Intersect(Selection, ).Address '记录当前选区的地址
taddress = '再记录当前工作表已用区域的地址
'添加一个辅助工作表,将当前已用区域对应的地址赋值0,将前选区对应的地址赋值"=0",从而形成差异(一为常量一为公式)
With
.Range(taddress) = 0
.Range(raddress) = "=0"
'记录常量的地址
raddress = .Range(taddress).SpecialCells(xlCellTypeConstants, 1).Address
.Delete '删除工作表
End With
'选择已记录的常量所对应的区域
(raddress).Select
= True
= True
End Sub
以上过程中设置了四种防错的措施:首先判断工作表是否被保护,在保护状态下无法选择反向区域;然后判断当前是否选择了单元格,未选择单元格时不存在反向区域;再之后计算选区的行数与列数,如果等于Excel的最大行数与最大列数,表示已全选工作表,它仍然不存在反向区域;最后判断选区与当前表已用区域的关系,如果不存在交集或者完全重合,那么也不存在反向区域。
而对于其它的情况下,则使用一个辅助工作表来完成记录反向区域的地址。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“反向选择”,那么瞬间可以完成反向选择。图和图是反向选择前后的对比效果,为了图特意对选区加了深灰色背景。
图 选择B3:B6 图 反向选择区域
〖语法补充〗:
(1)方法用于选择对象。可以单元格单元格,也可以选择区域,甚至同时选择多区域。但有别的激活Activate方法。Activate方法只能针对单个单元格、单个区域。
(2)属性用于获取单元格的地址,它的语法如下:
(RowAbsolute, ColumnAbsolute, ReferenceStyle, External, RelativeTo)
五个参数皆为可选参数,当忽略所有参数时表示A1样式的绝对引用。各参数含义如下:
表11-26 属性参数含义列表
名称
描述
RowAbsolute
如果为 True,则以绝对引用返回引用的行部分。默认值为 True
ColumnAbsolute
如果为 True,则以绝对引用返回引用的列部分。默认值为 True
ReferenceStyle
引用样式。默认值为 xlA1
External
如果为 True,则返回外部引用。如果为 False,则返回本地引用。默认值为 False
RelativeTo
如果 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和gif
filefilter1 = ("所有图片文件 (*.jpg;*.bmp;*.png;*.gif),*.jpg;*.bmp;*.png;*.gif")
'让用户选择待插入的文件,只能单选
filennames = (filefilter1, , "请选一个图片文件", , MultiSelect:=False)
'插入该图片,然后设置其上边距、左边距、宽度与高度皆与选区一致
With (filennames)
.Top =
. = msoFalse
.Width =
.Left =
.Height =
End With
End Sub
以上过程中使用了GetOpenFilename来获取图片文件名,然后根据图片文件全名插入该图片工作表,而不是使用插入图片的对话框(342)。这是因为插入图片的对话框无法限制用户一次只能插入一张图片,而且插入后的图片不便于控制。GetOpenFilename方法的优点在于随心所欲地定制文件格式,且插入后扔的图片也可以随心所欲控制它。
(3)返回工作表,选择B2:E5区域,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“插入图片且等于选区大小”,程序将弹出一个“请选择一个图片文件”的对话框,它支持png、jpg、gif和bmp四种格式的图片文件,见图所示。
从打开的对话框中任意选择一张图片,然后程序会将该图片插入到当前表,当其上边距、左边距以及高度、宽度都是与选区一致。图是选择的区域,而图是插入图片后的效果,它刚好适应选区。如果用户选择的是一个单元格,那么图片也会刚好嵌入该单元格中。
图 选择图片文件
图 选择单元格 图 插入图片覆盖选区
〖语法补充〗:
(1)属性返回或设置单元格的上边距(以磅为单位)。而属性返回或设置单元格的左边距(以磅为单位)。
(2)属性返回一个区域的宽度(以磅为单位)。属性返回一个区域的高度(以磅为单位)。
(3)方法可以显示一个标准的“打开”对话框,并获取用户文件名。它的语法如下:
(FileFilter, FilterIndex, Title, ButtonText, MultiSelect)
其中五个参数的含义如下:
表11-27 方法含义详解
名称
描述
FileFilter
一个指定文件筛选条件的字符串
FilterIndex
指定默认文件筛选条件的索引号,取值范围为 1 到由 FileFilter 所指定的筛选条件数目。如果省略该参数,或者该参数的值大于可用筛选条件数,则使用第一个文件筛选条件
Title
指定对话框的标题。如果省略该参数,则标题为“打开”
ButtonText
仅限 Macintosh
MultiSelect
如果为True,则允许选择多个文件名。如果为False,则只允许选择一个文件名。默认值为False
的一个极大的优点是可以任意筛选文件格式,也可以任何组合多种格式的文件。本例中默认显示四种格式的图片,如果需要分开显示,即一次筛选一种格式的图片,那么可以按以下方式设置筛选条件:
filefilter1 = "位图 (*.bmp),*.bmp,jpg图片 (*.jpg),*.jpg,png图片 (*.png),,Gif动画 (*.gif),*.gif"
设置效果如下,显示为四种格式,默认显示第一种,而对话框中显示的图片也根据用户选择的文件类型而变化。
图 显示四种格式的图片
如果需要既显示多个选项,其中某个选项又可以包含多种格式,那么需要对包含多种格式的选项利用分号连接。而且同时可以修改的第二参数设置默认选项为2。例如以下代码可以产生图中的效果:
filefilter1 = "位图 (*.bmp),*.bmp,其它图片 (*.jpg;*.png),*.jpg;*.pgn,Gif动画 (*.gif),*.gif"
图 显示三个选项共五种格式的文件
选择当前表已用区域的奇/偶数行
〖案例要求〗:选择当前表已用区域的奇/偶数行,由用户指定是奇数还是偶数。
〖知识要点〗:Columns、Rows
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 选择奇偶数行()
Dim i As Integer, rng As Range, Ji_Ou As Byte
On Error Resume Next
Star: '设定一个标签,当程序出错时返回此处
'指定奇偶
Ji_Ou = ("输入1:选择奇数行" & Chr(10) & "输入2:选择偶数行", "确认奇偶性", 1, , , , , 1)
'如果输入的值超出Byte范围则返回Star重新输入
IF > 0 Then : GoTo Star
'如果输入值不等于1而且不等于2则重新输入
IF Ji_Ou <> 1 And Ji_Ou <> 2 Then GoTo Star
'根据用户的输入值初始化变理
Set rng = Cells(Ji_Ou, 1).Resize(1, )
'循环,合并所有奇/偶数行
For i = Ji_Ou To Step 2
Set rng = Union(rng, Cells(i, 1).Resize(1, ))
Next i
'选择目标
End Sub
不要不是声明为变体型,那么让用户输入值时都需要防错,避免用户输入的值超过允许的范围而中断程序。本例中除了限制用户水能超过Byte的范围外,还人限制输入值不能是1或者2以外的任何值,否则会漏选部分区域。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“选择奇偶数行”,将弹出图所示对话框,让用户决定是选择奇数行还是偶数行,其默认值为1,表示奇数行。如果用户选择了奇数行,那么执行结果见图所示。
图 选择奇偶性 图 选择奇数行
〖语法补充〗:
(1)属性返回一个Range对象,它代表指定单元格区域中的行。
(2)属性返回一个Range对象,它代表指定单元格区域中的列。
删除当前表的空行
〖案例要求〗:将当前表已用区域中的空行删除。
〖知识要点〗:Row、Column
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 删除空行() '批量删除空行,适用于任何行任何列
Dim arr(), rng, lng As Long, lng2 As Long, i, j
= False
'记录当前表已用区域的行数和列数
lng =
lng2 =
ReDim arr(1 To lng) '声明一个数组,其下标为1,上标等于已用区域的行数
rng =
For i = 1 To lng '遍历每行
For j = 1 To lng2 '遍该行的每列
'如果该行非空则在数组中记录序号,否则赋值为空
'判断是否空行也可以使用工作表函数Counta,大家可以根据习惯选择
IF rng(i, j) <> "" Then arr(i) = i: Exit For
Next j
Next
' 在已用区域的右边一列添加辅助列,该列存放数组的值
'然后以该列为基准,对整个已用区域进行排序,从而实现将空白行移到最后面
With Cells(, + lng2).Resize(lng, 1)
.Value = (arr)
Key1:=Cells(, + lng2)
.Value = ""
End With
= True
End Sub
删除空行有很多方法,包括逐行检检,并将空行删除,以及合并所有空行再一次性删除,还有本例中排序方式将空行移到最后面。经过了多次测试,排序方式效率最高。
对于数组的声明和使用,在本书第13章和14章将进行详细讲解。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除空行”,工作表中的所有空行都被删除。图是删除前的测试数据,图是删除空行的效果。笔者利用2000行数据测试,执行时间不到一秒钟。
图 删除空行前 图 删除空行后
〖语法补充〗:
(1)属性返回区域中第一个子区域的第一行的行号。
(2)属性返回区域中第一个子区域的第一列的列号。
例如[b2:d100].Row等于2,而[b2:d100].Column也等于2,即取B2的行号与列号。
删除重复值
〖案例要求〗:图中为参赛人员名单,其中部分人员参加了多个项目的比赛。现需提取参赛人员名单,忽略重复值。
〖知识要点〗:RemoveDuplicates
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 删除重复值1() '2007专用
With Range("C1:C11")
'将所有参赛人员名单复制到C列
.Value = Range("A1:A11").Value
'删除重复值
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End Sub
以上过程将A列的名单复制到C列,然后对C列取唯一值。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除重复值”,执行后效果见图所示,重复值已经删除。
图 参赛者列表 图 获取唯一值
(4)以上代码中的RemoveDuplicates是Excel 2007新增功能,如果用户使用Excel 2003,或者希望代码可以在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 ([C1:C11], rng) = 0 Then
Item = Item + 1
Cells(Item, "C") = rng
End IF
Next
End 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 ([A1:A11], rng) = 0 Then
Item = Item + 1
Cells(Item, "A") = rng
End IF
Next
End Sub
〖语法补充〗:
(1)方法用于从区域中删除重复的值,可以是多条件,它的语法如下:
(Columns, Header)
其中两个可选参数的含义如下:
表11-28 方法参数详解
名称
描述
Columns
包含重复信息的列的索引数组。如果没有传递任何内容,则假定所有列都包含重复信息
Header
指定第一行是否包含标题信息。xlNo是默认值;如果希望Excel确定标题,则指定xlGuess
(2)RemoveDuplicates可以实现多条件去复重复值,即同时在多个列都重复才算重复。例如:图中,A、B列同时重复才算重复,,那么满足条件的是灰色背景的区域,其中“甲”虽然出现三次,但第三次出现时B列与前面的不同则不符合要求。双条件取唯一值代码如下:
Sub 删除重复() '2007专用
[A1:B11].RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
End Sub
以上过程中RemoveDuplicates的第一参数使用数组“Array(1, 2)”,表示同时两列都产生重复才满足条件。
图 双列重复的参赛列表 图 双条件取唯一值
将选区导出为图片
〖案例要求〗:将选区导入到硬盘中,保存为图片。
〖知识要点〗:RemoveDuplicates
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 将选区导出成图片()
Dim adds As String, filennames
'如果未选择单元格则退出程序
IF TypeName(Selection) <> "Range" Then Exit Sub
'记录选区地址,且将冒号修改为-,否则无法做为文件名
adds = Replace((0, 0), ":", "-")
'将文件复制为图片
1, 2
'贴粘图片
With Selection
'贴粘图片
.Copy
'新建一个图表
With (0, 0, , ).Chart
.Paste '将图片贴于图表中
'弹出一个保存图片的对话框,让用户录入新图片名字,默认单元格地址(冒号替换成-)
filennames = (adds, "JPG格式 (*.JPG),*.jpg,BMP格式(*.BMP),*.bmp,PNG格式 (*.PNG),*.png", 2, "保存图片文件")
'将图表导出成图片
.Export filennames
. '删除图表
End With
.Delete '删除图片
End With
'打开存放图片的文件夹
Shell " " & Left(filennames, Len(filennames) - InStr(StrReverse(filennames), "\")), vbMaximizedFocus
End Sub
以上过程首先将选区复制为图片、粘贴到工作表,然后新建一个与选区相同大小的图表,并将图片贴于图表中,最后通过Export方法将图表导出到硬盘中,保存为图片。默认的图片名为选区地址。
保存图片时提供三种可选格式,BMP格式最清晰,建议使用。
(3)返回工作表,假设需要史载图中A1:B2区域转换成图片,那么选择该区域,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将选区导出成图片”,在弹出的保存图片对话框中保持默认即可,生成的图片利用Win XP的图片浏览器查看效果如图所示:
图 复制A1:B2 图 已导出的名为A1-B2的图片
〖语法补充〗:
(1)方法将所选对象作为图片复制到剪贴板。它的语法如下:
(Appearance, Format)
其中第一参数表示图片的复制方式,包括以下两种方式:
表11-29 图片复制方式
名称
值
描述
xlPrinter
2
图片按其打印效果进行复制
xlScreen
1
图片尽可能按其屏幕显示进行复制
第二参数表示图片的格式,包括以下两种方式:
表11-30 图片格式
名称
值
描述
xlBitmap
2
位图(.bmp、.jpg、.gif)
xlPicture
-4147
绘制图片(.png、.wmf、.mix)
本例中“ 1, 2”表示将选区按屏幕显示进行复制,存为位图。
(2)利用本例方法,可以批量地将工作表中所有图片导出到硬盘,在本书最后章节详细介绍。
删除超链接
〖案例要求〗:根据用户的输入值决定删除当前表的超级链接或者删除所有工作表的超级链接。
〖知识要点〗:Hyperlinks
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 删除超级链接()
Dim sht As Worksheet, Opt As Byte
'防错
On Error Resume Next
Star: '设置签标
'让用户决定删除超级链接的范围
Opt = ("输入1:删除当前表超级链接" & Chr(10) & _
"输入2:删除所有工作表超级链接", "确认范围", , , , , , 1)
'如果有错误则返回,让用户重新输入
IF Err <> 0 Then : GoTo Star
IF Opt = 1 Then
'输入1时删除当前表所有链接
Else
'否则逐个工作表删除链接
For Each sht In Sheets
Next sht
End IF
End Sub
以上过程中用户输入1时删除当前表所有超级链接,录入2到255或者0时删除当前工作簿中所有工作表的超级链接。
(3)返回工作表,在单元格中输入和andy_qc@,Excel会自动将它们转换成超级链接,单击时可以打开邮件或者网址,见图所示:
(4)使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“删除超级链接”,输入1后单击“确定”按钮,当前表的所有超级链接瞬间消失,见图.
图 输入网址或邮件地址时自动产生超链接 图 删除链接后
〖语法补充〗:
(1)属性返回Hyperlinks集合,它代表区域中的所有超链接。通过可以统计当前表超链接个数。
(2)如果需要获取当前表所有超级链接地址
Sub 获取超级链接地址()
Dim i
For i = 1 To
MsgBox (i).Address
Next
End Sub
选择本表所有合并单元格
〖案例要求〗:选择当前表中所有合并单元格。
〖知识要点〗:MergeCells
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 全选合并单元格()
Dim rng As Range, rngg As Range
'遍历已用区域
For Each rng In
'如果具有合并属性
IF Then
'合并所有合并单元格为一个区域
IF rngg Is Nothing Then
Set rngg = rng
Else
Set rngg = Union(rngg, rng)
End IF
End IF
Next
'如果有合并单元格则选择合并单元格
IF Not rngg Is Nothing Then
End Sub
以上过程遍历已用区域,将所有具有合并属性的单元格合并成一个区域,然后选择该区域。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“全选合并单元格”,如果工作表中有合并单元格,将自动选择所有合并区域。图即为选择所有合并区域后的状态。为了突出选区,特意利用灰色填充选区。
图 选择所有合并区域
〖语法补充〗:
(1)属性用于判断单元格是否处于合并区域中,其值为True或Fasle。
朗读选区字符
〖案例要求〗:将选区的单元格字符朗读出来。
〖知识要点〗:Speak
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 阅读选区所有字符()
'如果选择对象是单元格则朗读
IF TypeName(Selection) = "Range" Then
End IF
End Sub
执行以上程序需要确保电脑装有声卡和连接音响,而且在所安装的OFFICE非精简版,否则可能不俱备朗读功能。
朗读单元格时区分中文和英文。如果发音引擎是英文,那么它会忽略中文,而英文会按照单词朗读出来;如果安装了中文的发音引擎,那么它可以朗读中文与英文,只不过对于英文不再是按单词朗读,而是逐个字母朗读。
(3)返回工作表,选择待阅读的区域,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“阅读选区所有字符”,音箱中可以传出朗读的声音。
〖语法补充〗:
(1)方法表示按行或列的顺序朗读单元格区域。它的语法如下:
(SpeakDirection, SpeakFormulas)
其中第一参数表示朗读的方向,按行或按列;第二参数表示公式的朗读方式,如果为True则朗读公式,如果为 False,则始终将朗读公式的值。
隐藏所有公式结果为错误的单元格
〖案例要求〗:图中部分公式的运算结果为错误值,现需要将所有错误隐藏,仅显示空白。
〖知识要点〗:Errors、NumberFormatLocal
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 隐藏所有错误公式()
Dim rng As Range
'遍历C2:C11区域
For Each rng In [c2:c11]
'将单元格的字体色设为单元格背景色
=
'设置单元格的数字格式,让错误值以外的值显示为黑色
= "[黑色]G/通用格式"
'忽略错误,隐藏绿色小三角
(1).Ignore = True
Next
End Sub
以上过程首先将所有单元格的字体色设置与背景色一致,然后利用单元格的数字格式将非错误值的字体设置为黑色,鉴于数字格式仅仅对非错误值生效,那么通过以上设置后就可以实现隐藏错误值。最后去掉绿色小三角。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏所有错误公式”,C2:C11区域中的所有错误值都将隐藏起来。图和图是隐藏错误前后的对比效果,
图 公式中的错误值 图 隐藏错误
〖语法补充〗:
(1)属性可以返回或设置一个单元格数字格式的格式代码。可以对单元格的数字格式赋值从而改变其显示值。例如单元格中当前显示“2008-9-9”,通过以下代码可以将它的显示值修改为“星期二”:
Sub 格式化为星期()
Range("A20").NumberFormatLocal = "AAAA"
End Sub
(2) 代表单元格中的错误对象,通过设置其Ignore属性可以控制是否显示小绿色三角符号。
快速添加日期批注且自动缩放
〖案例要求〗:利用快捷键在当前单元格生成日期批注,且批注外框刚才适应日期。
〖知识要点〗:、AddComment
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 快速添加日期批注()
'关闭屏幕刷新,防止闪屏
= False
'如果选择对象不是单元格则退出程序
IF TypeName(Selection) <> "Range" Then Exit Sub
'删除原有批注(如果有的话)
ActiveCell. ClearComments
'添加批注,内容为日期
With (Date & "")
.Visible = True '让批注可见
. '选择批注外框
= True '自动调整大小
.Visible = False '隐藏批注
End With
= True
End Sub
Sub 设置快捷键()
'为过程指定快捷键
"^e", "快速添加日期批注"
End Sub
第一个过程中需要对活动单元格添加批注,那么必须先删除已有批注,否则只能在已用批注的末尾添加新的文本。
对批注的外框设置为自动调整大小时,需要让批注可见,且选择该Shape,否则会设置不成功。
第二个过程用于对第一个过程设置快捷键。
(3)光标定位于第二个过程中,按下快捷键【F5】执行程序;
(4)返回工作表,激活任意单元格后按下快捷键【Ctrl+E】,将在该单元格产生日期批注,见图所示:
图 日期批注
〖语法补充〗:
(1)方法添加批注时其参数必须是文本,使用日期和数值都会产生错误。例如以下两种方式都会产生是错误:
123
Date
纠正错误可以采取两种方式:类型转换函数及连接空文本。
CStr(Date)
Date&""
修改后的两句代码都可以正常执行。
以逗号分为隔符将文本分列
〖案例要求〗:将单元格中的字符串以逗号为分隔符分置于多单元格中。
〖知识要点〗:TextToColumns
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 按分隔符将单元格分置多单元格()
[A1].TextToColumns Comma:=True, DataType:=xlDelimited, ConsecutiveDelimiter:=True, Space:=True
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“按分隔符将单元格分置多单元格”,A1的文本中如果有逗号那么会以逗号为分隔符分置于多单元格中。图和图是分列后前的数据:
图 分列前 图 分列后
〖语法补充〗:
(1)方法可以将包含文本的一列单元格分解为若干列。语法如下:
(Destination, DataType, TextQualifier, ConsecutiveDelimiter, Tab, Semicolon, Comma, Space, Other, OtherChar, FieldInfo, DecimalSeparator, ThousandsSeparator, TrailingMinusNumbers)
它的14个可选参数含义见下表:
表11-31 方法参数详解
名称
描述
Destination
一个Range对象,指定Excel放置结果的位置。如果该区域大于一个单元格,则使用左上角的单元格
DataType
将被拆分到多列中的文本的格式
TextQualifier
指定是将单引号、双引号用作文本分隔符还是不使用引号
ConsecutiveDelimiter
如果为True,则Excel将连续分隔符视为一个分隔符。默认值为 False
Tab
如果为True,则DataType为xlDelimited 并将制表符作为分隔符。默认值为 False
Semicolon
如果为True,则DataType为xlDelimited 并将分号作为分隔符。默认值为False
Comma
如果为True,则DataType为xlDelimited 并将逗号作为分隔符。默认值为False
Space
如果为True,则DataType为xlDelimited 并将空格字符作为分隔符。默认值为False
Other
如果为True,则DataType为xlDelimited 并将OtherChar 参数指定的字符作为分隔符。默认值为 False
OtherChar
(如果Other为True,则为必选项)。当Other为True 时的分隔符。如果指定了多个字符,则仅使用字符串中的第一个字符而忽略剩余字符
FieldInfo
包含单列数据相关分列信息的数组。对该参数的解释取决于 DataType 的值。如果此数据由分隔符分隔,则该参数为由两元素数组组成的数组,其中每个两元素数组指定一个特定列的转换选项。第一个元素为列标(从 1 开始),第二个元素是 xlColumnDataType 的常量之一,用于指定分列方式
DecimalSeparator
识别数字时,Excel使用的小数分隔符。默认设置为系统设置
ThousandsSeparator
识别数字时,Excel 使用的千位分隔符。默认设置为系统设置
TrailingMinusNumbers
以减号字符开始的数字
(2)如果不用分列,而是使用数组函数也可以完成的。代码如下:
Sub 分列()
Dim str As String, i
str = [a1].Text '将A1的值存入内存中
'将A1的值以逗号分分隔符转换成数组,然后遍历数组所有元素
For i = 1 To UBound(Split(str, ",")) + 1
'从数组中逐个取出值存入单元格
Cells(1, i) = Split(str, ",")(i - 1)
Next
End Sub
(3)分列方法只置的分号是半角状态下的,如果单元格中使用以全角逗号,那么本例的代码需要修改。
生成二级下拉选单
〖案例要求〗:利用图中D、E列的数据在单元格A1和B1产生二级下拉菜单,面且这两个下拉菜单必须有关联性,即第二个菜单跟随第一个单元格的显示值相应地变化。
〖知识要点〗:Validation
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 生成二级菜单()
Dim i, Str
'提取不重复的省名,组成一个字符串
For i = 1 To Cells(, "D").End(xlUp).Row
IF (Cells(1, "D").Resize(i, 1), Cells(i, "D")) = 1 Then Str = Str & Cells(i, "D") & ","
Next i
'将字符串加到有效性列表
With Range("A1").Validation
.Delete '删除以前的设置(假设有的话)
.Add Type:=xlValidateList, Formula1:=Left(Str, Len(Str) - 1) '自定义数据有效性来源
.ShowInput = True '显示下拉框
Range("A1") = Split(Left(Str, Len(Str) - 1), ",")(0) '设置其默认值
End With
End Sub
以上过程用于产生A1的下拉菜单,B的数据有效性需要在工作表事件中完成。双击工作表名进入工作表事件代码窗口,然后输入以下事件过程:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next
Dim i, rng As Range, Str As String
'只有允许A1触发工作表事件
IF <> "$A$1" Or Target = "" Then Exit Sub
Set rng = Range("D1:D" & Cells(, "D").End(xlUp).Row)
'串连该省的市名
For i = 1 To
IF 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 With
End Sub
以上过程是工用表级Change事件,当A1的值产生变化时,到工作表中D列查找A1的值,找到后将其E列中对应的市名串连起来,并做为数据有效性的来源。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成二级菜单”,此时A1产生默认值“广东”,B1则产生默认值“广州”;
(4)当单击单元格A1时会产生下拉菜单,罗列出所有省名,见图所示;如果选择“四川”,那么B1单元格将产生默认值“内江”,同时单击单元格B1时会产生与四川关联的所有市名,见图所示:
图 一级菜单 图 二级菜单
〖语法补充〗:
(1)属性返回一个Validation对象,该对象表示指定区域的数据有效性检验。对单元格添加有效性验证的语法如下:
表达式.Add(Type, AlertStyle, Operator, Formula1, Formula2)
五个参数的含义见下表所示:
表11-32 方法参数详解
名称
数据类型
描述
Type
XlDVType
有效性验证类型
AlertStyle
Variant
有效性验证警告的样式。可为以下 XlDVAlertStyle 常量之一:xlValidAlertInformation、xlValidAlertStop 或 xlValidAlertWarning
Operator
Variant
数据有效性验证运算符。可为以下XlFormatConditionOperator常量之一:xlBetween、xlEqual、xlGreater、xlGreaterEqual、xlLess、xlLessEqual、xlNotBetween 或xlNotEqual
Formula1
Variant
数据有效性验证等式中的第一部分
Formula2
Variant
当Operator为xlBetween或xlNotBetween 时,数据有效性验证等式的第二部分(其他情况下,此参数被忽略)
其中参数与有效性验证的类型相关,类型不同参数也不同。它们之间的关系如下:
表11-33 有效性验证类型与参数
有效性验证类型
参数
xlValidateCustom
Formula1必需,忽略 Formula2。Formula1 必须包含一个表达式,数据项有效时该表达式的值为 True,数据项无效时,该值为 False
xlInputOnly
使用AlertStyle、Formula1 或 Formula2
xlValidateList
Formula1必需,忽略 Formula2。Formula1 必须包含以逗号分隔的值列表,或对该列表的工作表引用
xlValidateWholeNumber、xlValidateDate、xlValidateDecimal、xlValidateTextLength 或 xlValidateTime
必须指定 Formula1 或 Formula2 之一,或两者均指定
(2)如果单元格中已经有有效性设置,那么需要删除以前的设置信息,否则会将生错误。
将产量批量转换成下拉菜单
〖案例要求〗:图中B列产品的产地,利用VBA将其转换成下拉菜单,有多少个地名即转换为多少个下拉菜单,而且在需要时可以还原。
〖知识要点〗:
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 将产地转换成菜单()
Dim rng As Range
'遍历区域
For Each rng In Range("B2:B6")
With
.Delete '删除以前的设置(假设有的话)
'自定义数据有效性来源,将/转换成逗号即可
.Add Type:=xlValidateList, Formula1:=Replace(, "/", ",")
.ShowInput = True '显示下拉框
rng = Split(, "/")(0) '设置其默认值
End With
Next rng
End Sub
以上过程首先将产地中的“/”替为成“,”,然后添加到数据有效性的公式中。然后为单元格设置默认值。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“将产地转换成菜单”。图和图是地名转换前后的对比效果。
图 转换前 图 转换后
(4)如果在某些条件下,需要将下拉菜单还原为图所示状态,那么可以改用以下过程:
Sub 还原()
Dim rng As Range
'遍历区域
For Each rng In Range("B2:B6")
'还原产地
rng = Replace(, ",", "/")
'删除有效性设置
Next rng
End Sub
〖语法补充〗:
(1)使Validation时,根据有效性的类型,可以有Formula1,可能有Formula2,也可能同时有Formula1和Formula2。
(2)当单元格的有效性类型为序列 时,中的公式应该使用半角状态下的逗号,如果使用全角逗号将无法产生多级菜单。
设计一个简单放大镜
〖案例要求〗: 选择任意单元格时产生一个放大镜,将选区的值放大N倍显示。
〖知识要点〗:Activate、CopyPicture
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
'声明Worksheet_SelectionChange事件
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'禁止递归
= False
On Error Resume Next '防错
'删除所有图片
'将选区复制到图片
Appearance:=xlScreen, Format:=xlPicture
'选择右边三列
(1, ).Offset(0, 3).Select
'贴选择图片
'设置图片的格式
With
.ScaleWidth 2, msoFalse, msoScaleFromTopLeft '增大两倍,你可以根据自己需求调整
. = 3 '设置前景色
. = 3# '指定边框宽度
. = msoLineSingle '线型
. = 4 '外框线颜色
End With
'激活单元格
= True '还原提示
End Sub
以上过程使用了工作表级别的SelectionChange事件,当选择单元格时触发事件。
工具首先将选区转换成图片,然后将图片粘贴到右边三个单元格的位置,并对图片设置格式,包括边框、填充色和大小,最后返回原选择区域。
(3)返回工作表,选择任意单元格或者区域,在右边第三列处会产生放大两倍的显示效果,该倍数可调。
图 放大多行一行数据 图 放大多行多列数据
〖语法补充〗:
(1)CopyPicture方法将单元格转成图片时,如果不需要打印出来,那么尽量使用“图片尽可能按其屏幕显示进行复制” 即参数xlScreen。
(2)方法用于激活一个单元格,不管其前置对象包括多少个单元格,仅激活其左上角单元格。
Names对象应用案例
Names即为名称对象。可以将单元格、区或转换成名称,也可以将公式、常量转换成名称。将区域转换成名称应用于代码中,可以让区域的表示法更具形象化,而公式或者数组常量转换成名称则可以简化输入,甚至用于突破Excel的公式长度限制。本节将介绍Names在工作中的一些常见应用。
罗列当前工作簿的所有名称
〖案例要求〗:将当前工作簿中所有定义的名称罗列在工作表。
〖知识要点〗:ListNames
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 罗列当前工作簿名称()
[A1].Resize(, 2).ListNames
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“罗列当前工作簿名称”,工作表中A列将出现当前工作簿所有名称,而B列是每个名称对应的引用值,见图所示。
图 罗列所有名称及其引用位置
〖语法补充〗:
(1)方法用于从指定区域的第一个单元格位置开始,将所有未隐藏的名称的列表粘贴到工作表上。
本节关于Names对象的所有实例代码参见光盘:..\ 第十一章\
利用名称引用其它表数据
〖案例要求〗:在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 = True
.ShowInput = True
End With
End Sub
在Excel 2007中,以上代码工作正常,引用ListNames工作表中的数据做为当前表A2的数据有效性来源,但是在Excel 2003中执行会产生错误。为了解决定个问题,需要借用名称来突破,修改后的代码如下:
Sub 引用其它表区域做为有效性系列数据源2() '借用名称实现与2003通用
'在工作簿中添加一个名称,引用ListNames工作表中的A1:A9数据
Name:="数据源", RefersToR1C1:="=ListNames!R1C1:R9C1", Visible:=False
'设置数据有效性,引用该名称
With Range("A2").Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Formula1:="=数据源"
.IgnoreBlank = True
.InCellDropdown = True
.ShowInput = True
End With
End Sub
以上过程在工作簿中建一个隐藏的名称,其数据源为其它工作表,然后将该名称做为数据有效性的引用源,从而实现引用其它工作表数据,在Excel 2003和Excel 2007中通用。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“引用其它表区域做为有效性系列数据源2”,如果工作表ListNames中A1:A9非空,那么A2单元格将产生下拉菜单。
图 引用其它表数据做为有效性数据源
〖语法补充〗:
(1)方法用于定义新名称,如果不带前置对象,那么默认为工作簿级名的名称。它的语法如下:
(Name, RefersTo, Visible, MacroType, ShortcutKey, Category, NameLocal, RefersToLocal, CategoryLocal, RefersToR1C1, RefersToR1C1Local)
(2)如果添加名称时不引用单元格,而是直接引用某个常量,那么可以对RefersToR1C1参数写入相应的公式即可:
Name:="圆周率", RefersToR1C1:="="
该代码添加一个名为“圆周率”、值为“”的名称。
隐藏当前工作簿包含“A”的所有名称
〖案例要求〗:图中B列产品的产地,利用VBA将其转换成下拉菜单,有多少个地名即转换为多少个下拉菜单,而且在需要时可以还原。
〖知识要点〗:Visible
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 隐藏含A的名称()
'声明一个名称对象变量
Dim nm As Name
'遍历名称
For Each nm In Names
'如果名称中包括“A”则隐藏
IF Like "*A*" Then = False
Next nm
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏含A的名称,那么过名称管理器无法再查看或者编辑包括“A”的名称。
〖语法补充〗:
(1)属性确定名称是否可见,当赋值为True时可见,赋值为False时不可见。
借用名称将区域数据引用到组合框
〖案例要求〗:向列表框添加区域中的数据。
〖知识要点〗:Add
〖实现步骤〗:
(1)在工作表中单击功能区【开发工具】\【插入】\【组合框(ActiveX控件)】,并在工作表中拖放,从而添加一个组合框;
(2)在A1:A8录入待显示在组合框中的数据,见图所示;
(3)进入VBE界上面,单击菜单【插入】\【模块】;
(4)在模块代码窗口输入以下代码:
Sub 设置引用源1()
Dim cell As Range
'遍历选区,将值逐个加入组合框
For Each cell In [a1:a8]
Sheets("Add2"). cell
Next
End Sub
以上代码可以实现将A1:A8的值逐个添加到组合框中,执行过程后单击组合框时可以产生下拉列表,见图所示。然而循环的方式效率较差,利用名称可以有效地处理这个问题
继续录入以下代码:
Sub 设置引用源2()
'添加名称,引用A1:A8的数据
"区域", [a1:a8]
'将名称赋值组合框
Sheets("Add2"). = "区域"
End Sub
执行以上过程后,可以实现同样效果,但借用名称的优势在于不需要循环而是一次添加调用完成。
图 在工作表添加组框 图 组合框中显示下拉列表
〖语法补充〗:
(1)执行本例中第一个过程后,再执行第二个过程可以得到相同结果,但如果执行第二个过程后就不能再执行第一个过程。因为对组合框的ListFillRange属性赋值后不允许再使用AddItem方法添加子项。但是可以利用以下语句清除ListFillRange属性,然后再添加子项:
Sheets("Add2"). = ""
(2)添加名称时,对名称命名需要遵循以下规则:
第一个字符不必须是英文字母或者汉字
不能在名称中使用空格、句点、惊叹号、或 @、&、$,# 等字符
名称的长度不可以超过 255 个字符
不能与Function、Sub过程名称相同
不能在范围的相同层次中使用重复的名称
设计三级下拉菜单
〖案例要求〗:利用图中的省名、市名、县名设计一个三级联动菜单。即单元格A1的省名变化时,B1的市名相应变化,C1县名也跟随B1的市名相应地变化。
〖知识要点〗:Add
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 生成三级菜单()
Dim cell As Range, 县名 As String, 市名 As String, Row_num As Integer, i, 省名
'删除当前表所有名称
For i = To 1 Step -1
(i).Delete
Next i
'记录数据来源区的行数
Row_num = Range("D1").
'通过循环添加新的名称,包括市名和省名
For Each cell In Range("E1:E" & Row_num)
'如果单元格与其下一行相同
IF (1, 0) = cell Then
'如果循环到某个市名最后出现的位置,那么将变量县名赋值空文本
IF (cell, Range("E1:E" & Row_num), 0) = Then 县名 = ""
'将所有市名按逗号串连起来
县名 = 县名 & """" & (0, 1) & """" & ","
Else
'否则添加名称,其Name为市名,引用源为所有县名与逗号、引号组成的字符串
Name:=, RefersToR1C1:="={" & 县名 & """" & (0, 1) & """" & "}", Visible:=False
End IF
'如果循环到某个省名最后出现的位置,那么将变量市名赋值空文本
IF ((0, -1), Range("D1:D" & Row_num), 0) = Then 市名 = ""
'如果省名与其下一行的省名相同,而且市名与其与一行的县名不同
IF (1, -1) = (0, -1) And cell <> (1, 0) Then
'如果循环到某个省名最后出现的位置,那么将变量市名赋值空文本
IF ((0, -1), Range("D1:D" & Row_num), 0) = Then 市名 = ""
'当前省中将所有市名串连起来
市名 = 市名 & """" & cell & """" & ","
Else
'否则添加名称,Name属性为省名,引用源为所有市名与逗号、引号组成的字符串
Name:=(0, -1).Text, RefersToR1C1:="={" & 市名 & """" & cell & """" & "}", Visible:=False
End IF
'将省名与逗号、引号组成字符串,使用其可以做为数据有效性的来源
IF ((0, -1), Range("D1:D" & Row_num), 0) = Then 省名 = 省名 & (0, -1) & ","
Next cell
'设置默认值
[a1:c1] = [d1:f1].Value
'添加A1的数据有效性,来源等于变量“省名”
With Range("A1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=省名
.InCellDropdown = True
End With
'将名为A1字符的名称替换掉花括号做为B1的数据有效性来源
With Range("B1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(([A1].Text).RefersToR1C1, "={", ""), """", ""), "}", "")
.InCellDropdown = True
End With
'将名为C1字符的名称替换掉花括号做为C1的数据有效性来源
With Range("C1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(([B1].Text).RefersToR1C1, "={", ""), """", ""), "}", "")
.InCellDropdown = True
End With
End Sub
以上过程用于生成省、市、县相关的名称,并将A1:C1设置数据有效性。还需要配合工作表事件完成二级菜单与一级菜单的关联性以及三级菜单与二级菜单的关联性。
(3)双击工程资源管理器中的工作表,进行工作表事件代码窗口,并输入以下事件过程代码:
Private Sub Worksheet_Change(ByVal Target As Range)
'如果当前单元格不是A1:B1则退出程序
IF > 1 Or > 2 Then Exit Sub
'如果当前单元格为空则退出程序
IF Target(1) = "" Then Exit Sub
IF = [A1].Address Then '如果是A1
'为B1修改数据有效性,随A1的变化而变化
With Range("B1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(([A1].Text).RefersToR1C1, "={", ""), """", ""), "}", "")
.InCellDropdown = True
'设置默认选项
Range("B1") = (Replace(Replace(Replace(([A1].Text).RefersToR1C1, "={", ""), """", ""), "}", ""), ",")(0)
End With
End IF
'为C1修改数据有效性,随B1的变化而变化
With Range("C1").Validation
.Delete
.Add Type:=xlValidateList, Formula1:=Replace(Replace(Replace(([B1].Text).RefersToR1C1, "={", ""), """", ""), "}", "")
.InCellDropdown = True
'设置默认选项
Range("C1") = (Replace(Replace(Replace(([B1].Text).RefersToR1C1, "={", ""), """", ""), "}", ""), ",")(0)
End With
End Sub
以上事件过程主要包含两项任务:设置B1和C1的默认值,以及A1变化时,修改B1单元格的有效性来源,B1变化时修改C1单元格的有效性来源。
(4)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“生成三级菜单”,工作表中A1:C1将产生默认的省市县名称;
(5)单击选择A1,从下拉列表中选择“四川”,那么B1单元格会相应地变化为“内江”,其下拉列表中也会产生与四川相关联的所有市名,C1单元格相应地变化为“资中县”,见图所示。如果从B1的下拉列表中选择“成都”,那么C1的默认值和下拉列表也会相对应变化为与成都相关联的所有县名。
图 省市县数据源 图 A1修改时B1和C1相应变化
〖语法补充〗:
(1)本例中利用名称生成三级联动菜单限于名称的字符限制,当市、县名太多时会出错,主要由县名、市名与逗号串连后的字符串长度决定。
Comments 对象应用案例
Comments 对象即批注,在工作中常利用批注来对单元格的数据做说明,本节对批注在工作中的实际应用做详细介绍。
批量将数据导入批注
〖案例要求〗:将用户指定的列的数据批注导入到选区的批注中,该数据来自当前列进行正数偏移或者负数偏移量的列中。
〖知识要点〗:AddComment、ClearComments
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 导入批注()
Dim rng As Range, Col As Integer
On Error Resume Next '防错
'如果选择对象不是单元格则退出程序
IF TypeName(Selection) <> "Range" Then Exit Sub
'如果选区列数大于1那么重新选择该区域的第一列
IF > 1 Then
MsgBox "只能对一列进行操作.", 64, "提示"
Selection(1).Resize(, 1).Select
End IF
Star: '设置一个标签
'让用户指定偏移列,以当前列为基准
Col = ("请输入偏移列号" & Chr(10) & "程序将对当前列的偏移列单元格字符加入当前导列批注中", "指定导入批注的列", 1, , , , , 1)
'如果输入数值超过Integer的有效范围则返回重新输入
IF Err <> 0 Then : GoTo Star
'输入的字符可以是负数也可以是正数,但按该值偏移后不能小于A列,不能大于已用区域的最后一列
IF Col < 1 - Or Col > Range([a1], ). - Then
MsgBox "只能在" & 1 - & "到" & Range([a1], ). - & "之间", 64, "提示"
GoTo Star '返回重新输入
End IF
For Each rng In Selection '遍历选择
IF Len((0, Col)) > 0 Then '只能非空单元格添加批注
'先清除以前的批注
((0, Col).Text) '添加新的批注
End IF
Next rng
End Sub
以上过程首先检查选择对象是否单元格,不是单元格则退出过程。同时检查选择的列数,因本工具是对选择的一列进行批量添加批注,如果用户选择多列的话,会自动重置选区为当前选区的第一列。
然后让用户输入偏移量,可以是正数、0和负数。当输入正数例如3时,表示将当前列向右偏移3列;当输入0时,表示当前列;当输入负数例如-5时,表示向左偏移5列。这里的偏移量是有限制的,即偏移后不能小于A列而产生错误引用,也不能超出已用区域的最大列造成插入空白批注,所以必须借用IF和Goto语句让用户输入正确的范围值后再继续。
(3)返回工作表,在工作表中选择C4:D7,然后选择使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“导入批注”,那么程序会提示“只能对一列进行操作”,见图所示;
图 选择多列时的提示
(4)单击“确定”按钮后,程序会自动定位于选区的第一列,并弹出一个对话框,让用户录入偏移量,默认值为1,见图所示:
图 指定偏移量
(5)如果输入10,程序会产生图所示的提示。其中-2和1的计算方式是:
当前列3减去最小列1等于2
已用区域最大值4减去当前列3等于1
单击“确定”按钮后程序会继续让用户输入偏移量,如果输入在-2到1之外的任意值,程序会永远地循环下去,直到用户输入正确值。
(6)输入偏移量1,表示将第四列的部门加入到选区中,那么执行结果如图所示,每个单元格的批注都对应于它右边的部门。
图 输入错误值时提示正确范围 图 将部门导入到区域列的批注
(7)如果选择A2:A10,并输入偏移量为3,那么所有姓名中导入的批注将会是他所在的部门,见图所示;
(8)也可以在空白列导入批注。例如选择F2:F10后输入1,表示导入G列的数据,但是当前表已用区域为A列到D列,所以程序会弹出图所示的提示,表示只能输入-5到-2,用户只需要输入-5到-2之间的任意值即可。
图 将部门导入到姓名列的批注 图 允许范围
〖语法补充〗:
(1)AddComment用于添加批注,它常与ClearComments同时使用。因为对已经有批注的单元格添加批注会产生错误而中断程序。
(2)添加批注时必须使用文本,所以本例中使用“(0, Col).Text”而不用“(0, Col)”或者“(0, Col).value”,否则将引用区域中只有数值时无法批入批注。
本节关于Comments对象的所有实例代码参见光盘:..\ 第十一章\
在所有批注末尾添加指定日期
〖案例要求〗:图中有4个批注,现需对当前表的所有批注末尾添加日期,日期由用户随意指定。
〖知识要点〗:Parent、Text
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 在批注中添加日期()
Dim dat As String, aa As Comment
'让用户指定一个日期,默认值为今日日期
dat = ("请指定一个日期:", "日期", Date, , , , , 2)
'遍历所有批注
For Each aa In
'在批注后面添加日期
Text:= & Chr(10) & dat
Next
End Sub
以上过程先首先让用户指定一个日期,默认值为当前系统日期。然后遍历所有批注,对批注的文字添加日期
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“在批注中添加日期”,程序会弹出一个对话框,让用户录入日期,默认值是当前系统日期,见图所示:
图 成绩表 图 指定日期
(4)如果使用默认日期,那么程序执行完成后,在当前表所有批注中将会添加日期,而且日期总会占据一个新行,见图所示:
(5)如果需要将日期添加到第一行,修改批注的代码可以修改为:
Text:=dat & ":" & Chr(10) & " " &
图 将日期添加到批注后 图 将日期添加到批注前
〖语法补充〗:
(1) 属性返回指定对象的父对象,即存放批注的单元格。
(2)方法用于设置批注文本。其语法如下:
(Text, Start, Overwrite)
三个参数含义如下:
表11-34 方法参数详解
名称
描述
Text
要添加的文本
Start
所添加文本的起始位置(字符数)。如果省略此参数,则删除批注中的所有现有文字
Overwrite
如果为 True,则覆盖现有文件。默认值是 False(插入文本)
利用第二参数和第三参数可以实现替换部分数据或者在中间插入部分字符。
为批注设置图片背景
〖案例要求〗:在单元格插入一个图片批注,图片由用户随意选择。
〖知识要点〗:Shape
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 插入图片标注()
Dim Pict As String
'先清除原有批注
'让用户选择一张图片
Pict = ("图片文件 (*.jpg; *.bmp),*.jpg; *.bmp")
'如果未选择图片则退出
IF Pict = "False" Then End
'添加批注
With
.Visible = False '隐藏批注
. Pict '填充图片
. = 100# '高度为100
. = 120# '高度为120
End With
End Sub
以上过程首先清除批注,然后由用户选择图片,最后将图片加入批注中,批注的大小可以调整。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“插入图片标注”后如果当前单元格有批注,那么会清除原有的批注,然后弹出一个对话框由用户选择图片,支持BMP和JPG两种格式。最后产生的效果如图所示:
图 插入图片批注
〖语法补充〗:
(1)属性返回一个Shape对象,它代表连接到指定批注的形状。
(2)Shape对象可使用Fill方法设置其填充颜色或者图片,图片可以使用任何有效的图片格式,但如果使用GIF动画,则不会产生动画效果。
添加个性化批注
〖案例要求〗:添加外观更具个性的批注,提供多种形状可选。
〖知识要点〗:Select
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 添加个性化批注()
Dim mystr As String, mystr2 As String, Comment As Comment
'清除原有批注
'让用户输入批注内容,默认值为用户名
mystr = InputBox("输入批注内容", "批注", , 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 = (mystr)
With Comment
.Visible = True '让批注可见
. True '选择批注
Select Case mystr2 '根据条件设置批注外观
Case 1
= msoShapeFlowchartSequentialAccessStorage
Case 2
= msoShapeFoldedCorner
Case 3
= msoShapeRightArrow
Case 4
= msoShapeRoundedRectangularCallout
Case 5
= msoShapePlaque
Case 6
= msoShapeDiamond
Case 7
= msoShape5pointStar
Case 8
= msoShapeCloudCallout
Case 9
= msoShapeOval
Case 10
= msoShapeHexagon
Case 11
= msoShapeOctagon
Case 12
= msoShapeCan
Case 13
= msoShapeSmileyFace
Case 14
= msoShapeHeart
Case 15
= msoShape8pointStar
Case 16
= msoShapeHorizontalScroll
Case 17
= msoShapeVerticalScroll
Case 18
= msoShapeWave
Case 19
= msoShapeDoubleWave
Case 20
= msoShape16pointStar
Case 21
= msoShape24pointStar
Case 22
= msoShapeFlowchartDocument
End Select
.Visible = False '隐藏批注
End With
'返回单元格
End Sub
以上过程首先清除原有批注,然后让用户录入批注的内容,并选择批注外观的形状。外观由数字1到22范围内的数值决定。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“添加个性化批注”然后程序会弹出一个对话框,让用户输入批注的内容,默认值为Office安装用户名,见图所示:
图 提示录入批注内容
(4)在对话框中录入批注内容“明天休息”,并单击“确定”,程序会继续弹出“批注外型”的对话框,由用户在22种形状中选择,默认值为1,见图所示:
图 选择批注的形状
(6)输入16,表示横卷形,然后单击“确定”按钮,活动单元格将产生图所示批注;如果输入的6,表示使用菱形,那么产生的结果见图所示:
图 横卷形批注 图 菱形批注
(7)本例中使用了Select Case条件语句,代码较长。鉴于变量Mystr2的规律性,可以使用Choose来简化过程。代码如下:
Sub 添加个性化批注2()
Dim mystr As String, mystr2 As String, Comment As Comment
'清除原有批注
mystr = InputBox("输入批注内容", "批注", , 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 = (mystr)
With Comment
.Visible = True
. True
= Choose(mystr2, msoShapeFlowchartSequentialAccessStorage, _
msoShapeFoldedCorner, msoShapeRightArrow, msoShapeRoundedRectangularCallout, msoShapePlaque, _
msoShapeDiamond, msoShape5pointStar, msoShapeCloudCallout, msoShapeOval, msoShapeHexagon, _
msoShapeOctagon, msoShapeCan, msoShapeSmileyFace, msoShapeHeart, msoShape8pointStar, _
msoShapeHorizontalScroll, msoShapeVerticalScroll, msoShapeWave, msoShapeDoubleWave, _
msoShape16pointStar, msoShape24pointStar, msoShapeFlowchartDocument)
.Visible = False
End With
End Sub
〖语法补充〗:
(1)方法用于选择对象,通常指图形对象。它的语法如下:
(Replace)
参数Replace表示用指定的对象替换当前所选内容,如果参数为False则可以同时选择多个对象。例如选择当前表所有图形对象,那么可以使用以下过程,如果其中参数False改用True则只能选择最后一个对象。
Sub 选择所有图形对象()
On Error Resume Next
Dim a As Shape
For Each a In
(False)
Next a
End Sub
批量修改当前表批注的外观
〖案例要求〗:将当前表所有批注修改外观。
〖知识要点〗:ShapeRange 、AutoSize
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 批量修改批注外观()
Dim Comment As Comment, Style As Byte
On 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 : GoTo Star
'如果值不在1到22这个范围则提示用户正确范围,并返回
IF Style < 1 Or Style > 22 Then MsgBox "只能在1到22之间", 64, "提示": GoTo Star
'遍历所有批注
For Each Comment In
With Comment
.Visible = True '设置为可见
. True '选择批注
'设置外观
= Choose(Style, msoShapeFlowchartSequentialAccessStorage, _
msoShapeFoldedCorner, msoShapeRightArrow, msoShapeRoundedRectangularCallout, msoShapePlaque, _
msoShapeDiamond, msoShape5pointStar, msoShapeCloudCallout, msoShapeOval, msoShapeHexagon, _
msoShapeOctagon, msoShapeCan, msoShapeSmileyFace, msoShapeHeart, msoShape8pointStar, _
msoShapeHorizontalScroll, msoShapeVerticalScroll, msoShapeWave, msoShapeDoubleWave, _
msoShape16pointStar, msoShape24pointStar, msoShapeFlowchartDocument)
'自动适应大小
= True
.Visible = False '隐藏批注
End With
Next
End Sub
以上过程利用For循环遍历所有批注,并逐个修改批注外观。
在选择外观时,有必要进行防错,否则用户输入0等等值会产生错误。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量修改批注外观”,程序会弹出图所示对话框,让用户从22种外观中选择一种,默认为第一种。执行结果见图所示:
图 选择外观 图 修改的批注
〖语法补充〗:
(1)属性返回一个Shape 对象,它代表连接到指定批注的形状。通过AutoShapeType属性可以设置Shape对象的外观。
(2)修改外观后,必须使用AutoSize调整其大小,否则部分文字可能看不到。
替换所有批注中的“计算机”为“电脑”
〖案例要求〗:将当前工作簿中所有工作表的批注中的“计算机”替换为“电脑”。
〖知识要点〗:Text
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 替换批注中的计算机为电脑()
Dim Comment As Comment, Sht As Worksheet
'遍历所有工作表
For Each Sht In Sheets
'遍历所有批注
For Each Comment In
'替换批注中的字符串
text:=Replace(, "计算机", "电脑")
Next Comment
Next Sht
End Sub
以上过程利用循环嵌套方式遍历工作簿中所有批注,并逐一替换批注的字符。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“替换批注中的计算机为电脑”,那么工作簿中所有批注都会在瞬间完成字符替换。图和图是替换前后的批注对比。
图 替换批注字符前 图 替换字符替换后
〖语法补充〗:
(1)Replace函数可以将批注中的指定字符串替换成其它字符串,也可以删除指定的字符串,只需要Replace的第三参数使用空文本即可。
Sheets对象应用案例
Sheets对象集合是所有表对象的集合,它包括工作表和图表,但在VBA中与用户打交道的通常是工作表,设计图表时极少借用VBA来完成。本节对工作表的应用进行一些案例讲解。
添加汇总工作表
〖案例要求〗:在工作簿中添加新表,名为“汇总”,且新工作表位于最后一个工作表。
〖知识要点〗:Add
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 新建工作表()
'在最后一个工作表之后新建一个工作表,且为“汇总”
Dim sht As Worksheet
On Error Resume Next
Set sht = Sheets("汇总")
IF Err = 0 Then Exit Sub
(after:=Sheets()).Name = "汇总"
End Sub
以上过程在创建工作表前需要判断工作簿中是否存在同名工作表,否则程序会弹出错误对话框、中途中断程序,而且产生一个未命名的新建工作表。判断“汇总”工作表是否存在的方式是将该工作表对象赋值给对象变量,如果不产生错误则表示当前工作簿中有“汇总”表存在,否则不存在。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建工作表”,在工作簿中职果不存在“汇总”表将产生一个名为“汇总”的新表,而且该表总位于最末尾。
〖语法补充〗:
(1)方法用于新建工作表、图表或者宏表,新建的工作表将成为活动工作表。它的语法如下:
(Before, After, Count, Type)
四个参数都是可选参数。如果忽略所有参数,表示在当前活动工作表之前添加一个新工作表。各参数含义如下:
表11-35 方法参数详解
名称
描述
Before
指定工作表的对象,新建的工作表将置于此工作表之前
After
指定工作表的对象,新建的工作表将置于此工作表之后
Count
要添加的工作表数。默认值为 1
Type
指定工作表类型。可以为下列XlSheetType常量之一:xlWorksheet、xlChart、xlExcel4MacroSheet或xlExcel4IntlMacroSheet。如果基于现有模板插入工作表,则指定该模板的路径。默认值为 xlWorksheet
根据上表分析,如果需要插入一个新表,位于最前面位置,那么可以使用以下语句:
Sheets(1)
如果需要插入的新表位于第二个工作表之后,则用以下语句:
, Sheets(2)——第一参数只需要保留逗号即可
After:=Sheets(2)——完全忽略第一参数,直接对第二参数赋值,但必须注明After
如果需要在末尾新建三个图表,则用以下语句:
, Sheets(), 3, xlChart
本例文件参见光盘:..\ 第十一章\新建汇总工作表.xlsm
批量添加工作表且以本月日期命名
〖案例要求〗:以本月的每日日期为名批量创建工作表。
〖知识要点〗:Name
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 批量创建日期工作表()
'如果当前表工作表数据已经大于等于本月天数则退出程序
IF >= Day((Date, 0)) Then Exit Sub
'批量添加工作表,个数等于本月天数与现在工作表数量之差
, , Day((Date, 0)) -
Dim sht As Worksheet, Item As Byte
'遍历所有工作表
For Each sht In Sheets
'累加计数器
Item = Item + 1
'逐个工作表命名,等于本月每一天的日期
= Format((Date, -1) + Item, "mm月dd日")
Next sht
End Sub
以上过程在创建工作表之前判断工作表个数,如果大于等于本月天数则退出程序。然后批量创建工作表,使其个数与现在工作表之和等于本月天数。最后利用For循环逐个进行工作表命名。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量创建日期工作表”,将瞬间达成批量创建工作表的需求,且每个工作表皆按日期命名(测试月为五月),结果见图所示:
图 批量创建本月每日工作表
〖语法补充〗:
(1)属性表示表名称。可以直接通过等号对它赋值。赋值时尽量使用文本格式,如果是日期可能会被Excel转换为不可预料的格式,受控制面板格式的影响。
本例文件参见光盘:..\ 第十一章\以日期批量创建工作表.xlsm
迅速产生样表
〖案例要求〗:将“样表”工作表复制一分,并以今天之日期命名,从而避免每天设计表格的格式。样表见图所示。
〖知识要点〗:Copy
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 复制样表()
'声明一个工作表对象
Dim sht As Worksheet
'防错
On Error Resume Next
'将名称等于当前日期的工作表赋与变量
Set sht = Sheets(CStr(Date))
'如果在错误,则表示不存在该表,那么将“样表”复到最后,且取名为今日日期
IF Err <> 0 Then
Sheets("样表").Copy after:=Sheets()
Sheets().Name = Date
End IF
End Sub
以上过程因复制工作表后需要命名,那么必须检查是否存在同名工作表。所以在过程中使用了防错机制配合工作表对象变量查核错误值,如果错误值大于0则复制样表,否则表式已经存在。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“复制样表”,那么在工作表标签的最末处将产生复制一份样表,其格式与预设的样本完成一致,只需要录入数据即可,而表格的名称为今日日期,见图所示:
图 样表 图 复制样表并以日期命名
(4)本例代码也可以利用第10章的错误处理函数来完成,特别是需要多处使用工作表命名时。调用错误处理函数的方式来完成本例需求,代码如下:
Function IsSht(ShtName As String) As Boolean
On Error Resume Next
Dim sht As Worksheet
Set sht = Sheets(ShtName)
IsSht = (Err = 0)
End Function
Sub 复制样表2()
IF Not IsSht(CStr(Date)) Then
Sheets("样表").Copy after:=Sheets()
Sheets().Name = Date
End IF
End Sub
〖语法补充〗:
(1)方法用于将工作表复制到工作簿的另一位置。具体语法如下:
(Before, After)
第一参数Before表示将要在其之前放置工作表副本的工作表。如果指定了After,则不能指定 Before。第二参数表示将要在其之后放置工作表副本的工作表。
(2)如果方法既不指定Before参数也不指定After参数,则Excel将新建一个工作簿,其中包含复制的工作表。
本例文件参见光盘:..\ 第十一章\复制样表.xlsm
将当前表移到其基它工作簿
〖案例要求〗:将当前工作表移到“人事资料.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”最末尾位置
after:=Workbooks("人事资料.xlsx").Sheets()
End IF
End Sub
以上过程首先判断是否已打开“人事资料.xlsx”,如果True则移动工作表。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“移动工作表”,那么当前工作表会移到“人事资料.xlsx”工作簿的工作表标签末尾位置。
〖语法补充〗:
(1)方法用于将工作表移到工作簿中的其他位置。它的语法如下:
(Before, After)
第一参数Before表示在其之前放置移动工作表的工作表,可以是本工作簿的工作表,也可以是其它工作簿的工作表。如果是其它工作簿的工作表,那么需要将工作簿名完整地指定,但不需要路径。如果指定了After参数,则不能指定Before参数。
(2)移动工作表后,VBA会激活目标工作簿,同时该工作表也处于激活状态。
(3)如果既不指定 Before 也不指定 After,Microsoft Excel 将新建一个工作簿,其中包含所移动的工作表。
本例文件参见光盘:..\ 第十一章\移动工作表.xlsm
除“目录”工作表外隐藏其它所有工作表
〖案例要求〗:除“目录”工作表外隐藏其它所有工作表。
〖知识要点〗:Visible
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 隐藏目录以外的所有工作表()
'声明对象变量
Dim sht As Worksheet
'遍历工作表
For Each sht In Sheets
'如果工作表名称不等于“目录”则隐藏
IF <> "目录" Then = xlSheetVeryHidden Else = xlSheetVisible
Next sht
End Sub
以上过程使用For循环遍历所有工作表,然后检查工作表名字是否“目录”,对于“目录”以外的所有工作表进行深度隐藏。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏目录以外的所有工作表”,当前工作簿中“目录”以外的工作表全部隐藏。
〖语法补充〗:
(1)属性可以返回或设置可工作表的可见性。它有三种状态可选:
表11-36 工作表可见性常量详解
常量
值
含义
xlSheetHidden
0
隐藏工作表,用户可以通过菜单取消隐藏
xlSheetVeryHidden
2
隐藏对象,以便使对象重新可见的唯一方法是将此属性设置为 True(用户无法使该对象可见)
xlSheetVisible
-1
显示工作表
(2)本例中设置“目录”以外的所有工作表为xlSheetVeryHidden,表示深度隐藏,无法通过工作表标签中的【取消隐藏】菜单来设置该工作表的隐藏属性。
本例文件参见光盘:..\ 第十一章\隐藏目录以外的工作表.xlsm
分别计算工作表数量和图表数量
〖案例要求〗:分别计算工作表数量和图表数量。
〖知识要点〗:Count
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 统计工作表与图表个数()
MsgBox "当前工作簿中有:" & Chr(10) & Chr(10) & _
"工作表:" & & "个" & Chr(10) & _
"图 表:" & & "个"
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“统计工作表与图表个数”,程序会弹出图所示对话框,,表示工作表个数和图表个数。
图 统计工作表和图表个数
〖语法补充〗:
(1)属性代表工作表集合中对象的数量,仅仅代表工作表的数量。
(2)属性代表图表集合中对象的数量,仅仅代表图片表的数量。Charts加上Worksheets组合成Sheets对象集合。
本例文件参见光盘:..\ 第十一章\计算工作表与图表数量.xlsm
建立带链接功能工作表目录且通过快捷键返回目录
〖案例要求〗:建立带链接功能的工作表目录且通过快捷键返回目录。
〖知识要点〗:Hyperlinks
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 创建目录()
'关闭屏幕更新加快速度
= False
Dim i As Integer, Sht_Count
'如果不存在“目录”则添加目录工作表
IF Not IsSht("目录") Then (Sheets(1)).Name = "目录"
Sht_Count = '获取工作表数量
For i = 2 To Sht_Count '遍历工作表
'在“目录”工作表添加链接
Sheets("目录"). Anchor:=Sheets("目录").Cells(i - 1, 2), Address:="", SubAddress:="'" & Sheets(i).Name & "'!A1", TextToDisplay:=Sheets(i).Name, ScreenTip:="单击打开:" & Sheets(i).Name
Next i
'恢复屏幕更新
= True
'为返回目录过程指定快捷键为【Ctrl+J】
"^j", "返回目录"
End Sub
'声明一个函数,用于判断是否存在某个指定名称的工作表
Function IsSht(ShtName As String) As Boolean
On Error Resume Next
Dim sht As Worksheet
Set sht = Sheets(ShtName)
IsSht = (Err = 0)
End Function
Sub 返回目录()
'如果有“目录”则返回“目录”工作表
IF IsSht("目录") Then Sheets("目录").Select
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“创建目录”,在工作簿中将添加一个新工作表,表中B列存放当前工作簿所有工作表的目录,当鼠标箭头指定目录时,将提示单击打开某工作表,见图所示:
图 创建带链接的工作表目录
(4)单击B4单元格,程序会自动跳转到工作表“周四”;
(5)按下快捷键【Ctrl+J】返回“目录”工作表。
〖语法补充〗:
(1)方法可向指定的区域加超链接。它的语法如下:
Add(Anchor, Address, SubAddress, ScreenTip, TextToDisplay)
其中五个参数的含义如下:
表11-37 Hyperlinks Add参数详解
名称
必选/可选
描述
Anchor
必选
超链接的位置。可为 Range 或 Shape 对象
Address
必选
超链接的地址
SubAddress
可选
超链接的子地址
ScreenTip
可选
当鼠标指针停留在超链接上时所显示的屏幕提示
TextToDisplay
可选
要显示的超链接的文本
(2)也可以通过方法添加网址链接。例如添加一个打开163新闻的链接,代参天如下:
Anchor:=[a1], Address:="", TextToDisplay:="163新闻"
以上代码表示在单元格中显示“163新闻”,而链接网址为。
(3)也可以利用方法添加邮件地址链接。例如单击后向Andy_qc@发送邮件,那么代码如下:
Anchor:=[a1], Address:="mailto:andy_qc@", TextToDisplay:="发邮件"
以上代码表示单元格中显示“发邮件”,单击后可以打开发邮件的窗口,且收件人是“andy_qc@”。
本例文件参见光盘:..\ 第十一章\创建工作表目录.xlsm
对当前表已用区域设置背景图片
〖案例要求〗:对当前表已用区或设置一幅背景图片,而其它区域则不产生图片。
〖知识要点〗:SetBackgroundPicture
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 为已区用域设定背景图片()
Dim Pic As String, rng As Range
'弹出对话框,让用户选择一张做为背景的图片
Pic = ("图片文件 (*.jpg; *.bmp),*.jpg; *.bmp")
'如果未选择图片则退出
IF Pic = "False" Then End
'为当前表设置背景图片
Filename:=Pic
Set rng =
'将多余的列设置为白色
Range(Cells(1, Range([a1], rng). + 1), Cells(1, )). = 2
'将多余的行设置为白色
Rows((Range([a1], rng). + 1) & ":" & ). = 2
End Sub
以上过程首先让用户选择一张图片,可以是Jpg或者Bmp两种格式之一。然后利用etBackgroundPicture方法将图片设置为工作表的背景,且将已用区域以外的区域填充白色,从而实现只能在已用区域看到图片背景。
过程中使用“Range([a1], rng) ”来获取已用区域的列数,而不用Rng. 是为了确保代码的通用性。因为用户的已用区域不一定多A1开始,可能A列或者第1行不属于已用区域,在此情况下会差生误差,将有数据的列或者行也设置白色背景。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“为已区用域设定背景图片”,程序会弹出一个对话框让用户选择图片。当选择图片后在工作表的已用区域将产生图片背景,而其它区域则不会有图片背景,见图所示:
图 仅对已用区或添加背景色
〖语法补充〗:
(1)方法用于为工作表设置背景图形。它的语法如下:
Worksheet..SetBackgroundPicture(Filename)
其中参数Filename表示图片的完整路径,可以使用的格式很多,但若使用GIF动画却无法产生动画效果。
(2)对已用区域之外的区域设置白色背景也可以改用以下方法:
Range(Columns(), Columns().End(xlToLeft).Offset(0, 1)). = 2
Range(Rows(), Rows().End(xlUp).Offset(1, 0)). = 2
本例文件参见光盘:..\ 第十一章\设置背景图片.xlsm
批量命名工作表
〖案例要求〗:根据单元格的值对工作表进行批量命名。
〖知识要点〗:Name
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 批量改名()
Dim sht As Worksheet, Item As Integer
'如果单元格个数不等于非空单元格个数则退出程序(也就是确保没有空单元格)
IF [a1:a5].Count <> ([a1:a5]) Then Exit Sub
'首先判断区域中是否存在重复值,如果不重复数据个数等于单元格个数,那么才执行命名
'计算单元格中不重复数据个数(所有单元格都非空)的方法是使用数组公式,Evaluate直接将数组公式求值
IF ("=SUMPRODUCT(1/COUNTIF(A1:A5,A1:A5))") = [a1:a5].Count Then
'遍历工作表
For Each sht In Sheets
Item = Item + 1
'改名,将单元格的值导入到工作表名称
= Cells(Item, 1)
Next sht
End IF
End Sub
以上过程首先判断区域中是否存在空单元格,然后判断是否存在重复值,只有区域中所有单元格非空且不重复值也可以执行过程,否则命名时会产生错误。
在计算区域中不重复值个数时调用了数组公式,读者可以将这种思路用到其它类似的场合。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量改名”,工作簿中所有工作表都瞬间完成重命名,见图所示:
图 从区域中获取数据对工作表批量命名
〖语法补充〗:
(1)Evaluate的参数可以使用名称,也可以使用公式。在本例中使用了一个公式,将该公式直接录入到单元格也可以正确运行。而且不用等号Evaluate也可以计算出结果。
(2)也可以利用方括号“[]”来替代Evaluate,例如使用以下代码也可以正确获得结果5:
MsgBox [=SUMPRODUCT(1/COUNTIF(A1:A5,A1:A5))]
本例文件参见光盘:..\ 第十一章\工作表批量命名.xlsm
隐藏所有工作表非使用区
〖案例要求〗:将所有工作表的非使用区域都隐藏起来。
〖知识要点〗:UsedRange
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 隐藏非使用区()
Dim Rownum As Long, ColNum As Long, cell As Range, j As Long, k As Long, Sht As Worksheet, ans As Byte
Dim Col As Long, Row As Long
On Error Resume Next
'记录最大行与最大列(通用于Excel 2003和2007)
Row =
Col =
Star:
'让用户选择隐藏还是取消隐藏
ans = ("输入1:隐藏本工作簿中所有工作表的非使用区域" & Chr(10) & "输入2:取消所有工作表隐藏区", "请选择执行方式", 2, , , , , 1)
IF Err <> 0 Then GoTo Star
IF ans <> 1 And ans <> 2 Then MsgBox "输入错误,请输入1或者2", 65, "友情提示": GoTo Star
IF ans = 1 Then GoTo 隐藏
还原:
= False
For Each Sht In Worksheets '在所有表中循环,忽略图表
= False '取消隐藏
= False
Next Sht
= True
Exit Sub
隐藏:
On Error GoTo err
= False
For Each Sht In '遍历所有工作表(忽略图表)
'如果工作表中没有数据则运行black标签处的语句
IF () = 0 Then GoSub black
'记录已用区域行数和列数
Rownum = (Sht.[a1], ).
ColNum = (Sht.[a1], ).
j = 1
'通过循环获取已用区域最后一行的行号(取每列的最大值)
For Each cell In (("a" & Row), (Row, ColNum))
IF j < (xlUp).Row Then j = (xlUp).Row
Next
k = 1
'记录已用区域最后一列的列号
For Each cell In ((1, Col), (Rownum, Col))
IF k < (xlToLeft).Column Then k = (xlToLeft).Column
Next
'隐藏已用区域以外的所有区域
((1, k + 1), (1, Col)). = True
((j + 1, 1), (Row, 1)). = True
Next Sht
= True
Exit Sub
black:
'将所有行列都隐藏
= True
= True
Return
err:
On Error Resume Next
'当有图形对象覆盖空或者空列时,该行、列隐藏将出错
MsgBox "不能将对象移至工作表以外的区域。" & Chr(10) & "【" & & "】 操作不成功!" _
& Chr(10) & "原因有两个:可能有图形对象位于工作表的空白区;" & Chr(10) & _
"也可能最后一行或列有批注占用了工作表的空白区。", 65, "友情提示"
Resume Next
End Sub
以上过程首先弹出一个对话框由用户选择隐藏还是取消藏,然后根据输入值决定执行方式。在隐藏非使用区域时利用方法来判断使用区域的最大行和最大列,这种方相对于定位或者Usedrange方法效率上更低一些,然而当用户的工作表中有大量带格式的空单元格时,本方法就会体现出优势了——准确度更高;而在取消所有工作表的隐藏区域时则不需要任何判断,直接对所有行、所有列的Hidden属性赋值即可。
隐藏行与列时,需要注意两点:图表不存在行与列,所以必须在工作表中循环,忽略图表;如果工作表中有任意图形对象、批注、嵌入式图表在已用区域之外,那么该表即无法完成隐藏非使用区,因为Excel无法让图形对象处可见行、列之外。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“隐藏非使用区”,程序会弹出一个对话框,让用户选择执行方式,默认值为2,见图所示。
(4)如果输入值不等于1也不等于2,那么程序会返回对话框要求继续输入,直到输入值在允许范围为止。如果输入数值1,那么程序会将所有工作表中非使用区域隐藏起来,仅能看到有数据的行与列,见图所示:
图 选择执行方式 图 隐藏非使用区后
〖语法补充〗:
(1)属性返回一个Range对象,该对象表示指定工作表上所使用的区域。已用区域是以单元格是否存在数据和格式为依据。如果单元格有数据或者无数据但保留了用户设置的格式信息,包括字体色、背景色、字体加粗等等,那么该单元格就属于已用区域。
(2)工作表才有UsedRange属性,图表不存在UsedRange属性。
本例文件参见光盘:..\ 第十一章\隐藏所有工作表非使用区.xlsm
Workbooks对象应用案例
Workbooks对象集合包括包有工作簿,工作簿是一个单独的文档,保存完整的报表信息,不像工作表或单元格一样附属于其它对象中。本节对工作簿对象进行详细地实例演示。
新建工作簿且对其命名为今日期
〖案例要求〗:新建一个工作簿,且另存为当前系统日期,保存路径由用户选择。
〖知识要点〗:Add、
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 新建工作表簿以日期命名()
'新建一个工作簿
'发送命令,内容为当前日期,发送到下一句代码所打开的对话框中
Format(Date, "yyyy-mm-dd")
'打开另存为对话框
(5).Show
End Sub
以上过程首先建立一个工作簿,然后打开“另存为”对话框,且发送当前日期到对话框,成为默认日期名称。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“新建工作表簿以日期命名”,那么程序会立即新建一个工作簿,且打开图所示“另外为”对话框,让用户选择保存路径及文件名,默认文件名为当前系统日期。
图 以日期为默认名保存文件
〖语法补充〗:
(1)方法新建一个工作簿,新工作簿将成为活动工作簿。它的语法如下:
(Template)
(2)方法的参数Template确定如何创建新工作簿。如果此参数为指定现有 Excel文件名的字符串,那么创建新工作簿将以该指定的文件作为模板。如果此参数为常量,新工作簿将包含一个指定类型的工作表。可为以下 XlWBATemplate 常量之一:xlWBATChart、xlWBATExcel4IntlMacroSheet、xlWBATExcel4MacroSheet 或 xlWBATWorksheet。
例如新建一个仅仅包括一个图表的工作簿,那么可以使用以下语句:
xlWBATChart
新建一个仅包括一个宏表的工作簿,那么可以使用以下语句:
xlWBATExcel4IntlMacroSheet
如果当前已打开一个名为“预算.xls”或者“预算.xlsx”的工作簿模板文件,那么以下语法可以建立一个以该文件为模板,且名为“预算1”的工作簿:
"预算"
本例文件参见光盘:..\ 第十一章\新建工作簿另存为日期.xlsm
将当前工作簿另存且加密为123
〖案例要求〗:将当前工作簿另存到C盘,且文件打开密码为123。
〖知识要点〗:SaveAs
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 另存且密码为123()
Filename:="C:\工作簿", Password:=123, FileFormat:=xlOpenXMLWorkbookMacroEnabled
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“另存且密码为123”,那么当前工作簿会另存在C:\工作簿,且打开时该文件时会提示密码。
(4)如果使用Excel 2003,那么不支持xlsm格式的文件,必须改用以下代码:
Sub 另存且密码为123()
Filename:="C:\工作簿", Password:=123
End Sub
(5)如果需要代码通用于Excel 2003和2007,那么需求通过IF配合来完成。代码如下:
Sub 另存且密码为123()
IF * 1 = 12 Then
Filename:="C:\工作簿", Password:=123, FileFormat:=xlOpenXMLWorkbookMacroEnabled
Else
Filename:="C:\工作簿", Password:=123
End IF
End Sub
〖语法补充〗:
(1)方法用于另存工作簿,在另一不同文件中保存对工作簿所做的更改。它的语法如下:
(FileName, FileFormat, Password, WriteResPassword, ReadOnlyRecommended, CreateBackup, AccessMode, ConflictResolution, AddToMru, TextCodepage, TextVisualLayout, Local)
表11-38 方法参数详解
名称
描述
Filename
一个表示要保存文件的文件名的字符串。可包含完整路径,如果不指定路径,Excel将文件保存到当前文件夹中
FileFormat
保存文件时使用的文件格式。对于现有文件,默认采用上一次指定的文件格式;对于新文件,默认采用当前所用 Excel 版本的格式
Password
它是一个区分大小写的字符串(最长不超过 15 个字符),用于指定文件的保护密码
WriteResPassword
一个表示文件写保护密码的字符串。如果文件保存时带有密码,但打开文件时不输入密码,则该文件以只读方式打开
ReadOnlyRecommended
如果为 True,则在打开文件时显示一条消息,提示该文件以只读方式打开
CreateBackup
如果为 True,则创建备份文件
AccessMode
工作簿的访问模式
ConflictResolution
一个 XlSaveConflictResolution 值,它确定该方法在保存工作簿时如何解决冲突。如果设为 xlUserResolution,则显示冲突解决对话框。如果设为 xlLocalSessionChanges,则自动接受本地用户的更改。如果设为 xlOtherSessionChanges,则自动接受来自其他会话的更改。如果省略此参数,则显示冲突处理对话框
AddToMru
如果为 True,则将该工作簿添加到最近使用的文件列表中
Local
不在美国英语版的Excel中使用
TextVisualLayout
不在美国英语版的Excel中使用
Local
如果为 True,则以Excel(包括控制面板设置)的语言保存文件。如果为 False(默认值),则以VBA的语言保存文件,其中VBA 通常为美国英语版本,除非从中运行的VBA 项目是旧的已国际化的 XL5/95 VBA 项目
本例文件参见光盘:..\ 第十一章\另存且密码为
工作簿拆分
〖案例要求〗:将工作簿按工作表拆分,每个工作表存为一个单独的工作簿,保存路径由用户选择。
〖知识要点〗:Close、SaveAs
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 拆分工作簿()
Dim fd As FileDialog, path As String, sht As Worksheet
'弹出对话框,让用户选择文件夹
Set fd = (msoFileDialogFolderPicker)
'如果选择了文件夹则记录地路径
IF = -1 Then
path = (1) & IIF(Right((1), 1) = "\", "", "\")
Else: Exit Sub
End IF
'遍工作表
For Each sht In Sheets
'将工作表复制到新工作簿中(相当于新建一个文件,再将当前表复制到其中,但新工作簿中仅仅包括一个工作表)
'将新工作簿保存在刚才选择的路径中,且以工作表名做为工作簿名
path & , xlWorkbookDefault
'关闭工作簿
Next sht
End Sub
以上过程首先让用户选择一个存放工作簿的路径,如果未选择则退出程序;如果选择了路径则逐个复制工作表到新工作簿中,将工作簿保存在该路径下,以工作表名做为工作簿名称。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“拆分工作簿”,程序会弹出一个“浏览”对话框,假设选择C盘,那么程序会将当前工作簿中每个工作表保存为一个工作簿,以工作表名命名。图和图分别为拆分前的工作簿和拆分后的所有文件列表:
图 拆分前的工作簿 图 拆分后的工作簿
〖语法补充〗:
(1)方法表示关闭工作簿。它的语法如下:
表达式.Close(SaveChanges, Filename, RouteWorkbook)
其中三参数的含义如下:
表11-39 方法参数详解
名称
描述
SaveChanges
如果工作簿中没有改动,则忽略此参数。如果工作簿中有改动但工作簿显示在其他打开的窗口中,则忽略此参数。如果工作簿中有改动且工作簿未显示在任何其他打开的窗口中,则由此参数指定是否应保存更改。如果设为 True,则保存对工作簿所做的更改。如果工作簿尚未命名,则使用 FileName。如果省略 Filename,则要求用户提供文件名
Filename
以此文件名保存所做的更改
RouteWorkbook
如果工作簿不需要传送给下一个收件人(没有传送名单或已经传送),则忽略此参数。否则,Microsoft Excel 根据此参数的值传送工作簿。如果设为 True,则将工作簿传送给下一个收件人。如果设为 False,则不发送工作簿。如果忽略,则要求用户确认是否发送工作簿。
(2)关闭一个未保存的工作簿时会提示用户是否保存,而的第一参数使用False时可以禁止该提示。
本例文件参见光盘:..\ 第十一章\拆分工作簿.xlsm
批量打开文件
〖案例要求〗:批量打开选定的文件。
〖知识要点〗:Open
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 批量打开文件()
Dim fd As FileDialog, Item As Integer
'弹出一个浏览文件的窗口,可以多选目标文件
Set fd = (msoFileDialogFilePicker)
'如果选择了文件
IF = -1 Then
'遍历所有文件
For Item = 1 To
'逐个打开文件
((Item))
Next Item
End IF
End Sub
以上过程会弹出一个“浏览”对话框,当用户选择文件后程序对选中的文件逐一打开。如果某文件有打开密码,将会中断,等待用户输入密码后继续执行。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“批量打开文件”,将弹出对个对“浏览”话框,通过鼠标拖动或者Ctrl、Shift键配合左右键单击都可以多选文件,见图所示:
图 选择待打开的文件
(4)单击对话框中“确定”按钮后,程序会打开所有选定的文件。
〖语法补充〗:
(1)方法用二打开一个工作簿,它的语法如下:
(FileName, UpdateLinks, ReadOnly, Format, Password, WriteResPassword, IgnoreReadOnlyRecommended, Origin, Delimiter, Editable, Notify, Converter, AddToMru, Local, CorruptLoad)
方法的所有参数都是可选参数。各参数含义如下:
表11-40 方法参数详解
名称
描述
CorruptLoad
String 类型。要打开的工作簿的文件名
UpdateLinks
指定更新文件中链接的方式。如果省略此参数,则提示用户指定链接的更新方式
ReadOnly
如果为 True,则以只读模式打开工作簿。
Format
如果Excel正在打开文本文件,则由此参数指定分隔符。如果省略此参数,则使用当前的分隔符
Password
一个字符串,包含打开受保护工作簿所需的密码。如果省略此参数并且工作簿已设置密码,则提示用户输入密码
WriteResPassword
一个字符串,包含写入受保护工作簿所需的密码。如果省略此参数并且工作簿已设置密码,则提示用户输入密码
IgnoreReadOnlyRecommended
如果为 True,则不让Excel 显示只读的建议消息
Origin
如果该文件为文本文件,则此参数用于指示该文件来源于何种操作系统。可为以下 XlPlatform常量之一:xlMacintosh、xlWindows 或 xlMSDOS
Delimiter
如果该文件为文本文件并且 Format 参数为 6,则此参数是一个字符串,指定用作分隔符的字符
Editable
如果文件为 Excel 加载宏,则此参数为 True 时可打开该加载宏以使其在窗口中可见。如果此参数为 False 或被省略,则以隐藏方式打开加载宏,并且无法设为可见。本选项不能应用于由 Excel 或更高版本的 Excel 创建的加载宏
Notify
当文件不能以可读写模式打开时,如果此参数为 True,则可将该文件添加到文件通知列表。Excel 将以只读模式打开该文件并轮询文件通知列表,并在文件可用时向用户发出通知。如果此参数为 False 或被省略,则不请求任何通知,并且不能打开任何不可用的文件
Converter
打开文件时试用的第一个文件转换器的索引。首先试用的是指定的文件转换器;如果该转换器不能识别此文件,则试用所有其他转换器。转换器索引由 FileConverters 属性返回的转换器行号组成
AddToMru
如果为 True,则将该工作簿添加到最近使用的文件列表中。默认值为 False。
Local
如果为 True,则以Excel的语言保存文件。如果为 False(默认值),则以VBA的语言保存文件,其中VBA 通常为美国英语版本,除非从中运行 的 VBA 项目是旧的已国际化的 XL5/95 VBA 项目
CorruptLoad
可为以下常量之一:xlNormalLoad、xlRepairFile 和 xlExtractData。如果未指定任何值,则默认行为通常为普通加载,但如果 Excel 已尝试打开该文件,则可以是安全加载或数据恢复状态。首先尝试普通加载。如果 Excel 在打开文件时停止操作,则尝试安全加载状态。如果 Excel 再次停止操作,则尝试数据恢复状态
本例文件参见光盘:..\ 第十一章\批量打开工作簿.xlsm
导入文本文件到当前工作簿
〖案例要求〗:导入文本文件到当前工作簿第三个工作表中。
〖知识要点〗:Sheets
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 导入文件且以逗号分列()
'将文件导入后存入第三个工作表中
With (3).(Connection:="TEXT;C:\成绩表.txt", Destination:=(3).Range("$A$1"))
'以逗号进行分列
.TextFileCommaDelimiter = True
'更新外部数据
.Refresh BackgroundQuery:=False
End With
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“导入文件且以逗号分列”,那么C盘中名为“成绩表.txt”的文本文件中的数据将瞬间导入到当前工作表簿第三个工作表中。图和图是文本文件内容导入到Excel后的显示状态。
图 文本文件
导入文本文件到工作簿
〖语法补充〗:
(1)属性返回一个Sheets集合,它代表指定工作簿中所有工作表。利用参数Item可以访问其它某个工作表。
例如Worksheets("Book1").Sheets (2)表示Book2工作簿中第二个工作表
("2")表示当前活动工作表中名为“2”的工作表,而非第2个工作表
ThisWorkbook .Sheets(Date & "")表示VBA代码所在工作簿中名为今日日期的工作表
ThisWorkbook .Sheets(Date)表示VBA代码所在工作簿中序号为今日日期的工作表,日期转序号可以使用“Date * 1”。假设今天是2009年5月27日,那么该代码表示引用工作簿中第39960个工作表。这是Sheets的参数使用文本和日期的差异。
本例文件参见光盘:..\ 第十一章\导入文本文件.xlsm
保存并关闭本工作簿以外的工作簿
〖案例要求〗:保存并关闭本工作簿以外的工作簿。
〖知识要点〗:Name
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 保存并关闭本工作簿以外的工作簿()
Dim Wkb As Workbook
'遍历工作簿
For Each Wkb In Workbooks
'如果不是本工作簿
IF <> Then
'保存文件
'关闭
End IF
Next Wkb
End Sub
以上过程在打开了多个工作簿时可以保存并关闭代码所在工作簿以外的所有工作簿。如果工作簿已曾经保存过,则仅仅保留更新的信息,如果工作簿未保存过,则将工作簿保存在当前文件夹。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“保存并关闭本工作簿以外的工作簿”,那么其它工作簿会瞬间保存更新的数据并关闭。如果仅仅关闭不保存,那么条件语句中间的两句代码可以修改为一句:
(False)
〖语法补充〗:
(1)属性返回一个String值,它代表对象的名称,可读不可写。如果工作簿未保存,那么它的名称不带后缀名,例如“Book1”、“Book20”等等。当保存后会根据格式不同显示出后缀名,例如“生产表.xls”、“”等等。
(2)修改打开工作簿的名称唯一方法是Saveas方法另存工作簿。
本例文件参见光盘:..\ 第十一章\保存并关闭所有工作簿.xlsm
每30分钟备份工作簿
〖案例要求〗:将当前工作簿每30分钟备份一次。备份路径可选,每次备份的文件以“备份1”、“备份2”等等加原名做为文件名。
〖知识要点〗:Path、Name、Saveas
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Dim fd As FileDialog, path As String, File_name As String, File_Path As String
Sub 每30分钟备分工作簿()
'弹出对话框,让用户选择文件夹
Set fd = (msoFileDialogFolderPicker)
'如果选择了文件夹则记录路径
IF = -1 Then
path = (1) & IIF(Right((1), 1) = "\", "", "\")
Else
Exit Sub
End IF
'获取文件名与文件路径
File_name =
File_Path = & "\"
Call 备份
End Sub
以上过程让用户选择一个存放备份文件的路径,然后提取当前文件的文件名与路径(必须确保工作簿已经保存过)。
Sub 备份()
'声明一个静态变量
Static Item
'设置计划任何 每30分钟执行一次
EarliestTime:=Now + TimeValue("00:00:05"), Procedure:="备份"
Item = Item + 1
'关掉提示,防止弹出覆盖文件的对话框
= False
'备份到指定的文件夹中,且在原文件名之前添加前缀“备份1”\“备份2”等等
Filename:=path & "备份" & Item & File_name, CreateBackup:=False
'再返回原来路径,覆盖原文件
Filename:=File_Path & File_name, CreateBackup:= False
= True
End Sub
以上过程每30分钟执行一次,将当前工作簿另存到指定的备份文件夹中,再返回来有路径,覆盖原有文件。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“每30分钟备分工作簿”,程序会弹出一个对话框,让用户选择备份文件路径。假设选择C盘根目录,那么程序会每30分钟备份一次工作工作簿,当120分钟后在C盘将生成5个备份的文件,见图所示:
图 C盘中的备份文件
〖语法补充〗:
(1)属性它代表工作簿的完整路径,不包括末尾的分隔符和应用程序名称。例如工作簿“”保存在C盘中BCD文件夹中,那么它的Path属性为“C:\BCD”。
(2)VBA的Saveas方法有一个CreateBackup参数可以控制文件是否在保存时备份,然后笔者经过多次测试,在Excel 2007使用时无法确保能正常的备份,远远不如本例的代码容易控制,且可以随心所欲选择备份路径和文件名。
本例文件参见光盘:..\ 第十一章\每30分钟备份.xlsm
将当前工作簿备份到D盘
〖案例要求〗:将当前工作簿备份到D盘。
〖知识要点〗:SaveCopyAs
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 备份到D盘()
'如果未保存则显示“另存为”对话框
IF = "" Then (xlDialogSaveAs).Show
'备份文件
"D:\备份文件" &
End Sub
以上过程首先检查工作簿是否保存,如果未保存为显示另存对话框,让用户保存文件。然后将当前工作簿备份到D盘根目录中,且在原文件中加“备份文件”,不管执行多少次备份,仅仅产生一个备份。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“备份到D盘”,当前工作簿将备份到D盘根目录,且添加前置字符串“备份文件”。
〖语法补充〗:
(1)方法用于将指定工作簿的副本保存到文件,但不修改内存中的打开工作簿。其语法如下:
(Filename)
其中参数Filename表示备份文件的文件名,包含路径。
(2)SaveCopyAs用于备份文件,虽然它也另存文件,但不改变活动工作簿;而Saveas方法在另存工作簿后,会退出原有工作簿,激活另存后的新工作簿,这是两者的区别。
本例文件参见光盘:..\ 第十一章\当前工作簿备份到D盘.xlsm
清除所有打开工作簿的密码
〖案例要求〗:将当前打开的工作簿的密码清空。
〖知识要点〗:HasPassword
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 清除密码()
Dim Wkb As Workbook
'关掉警告提示,防止询问是否覆盖文件
= False
'遍历所有工作簿
For Each Wkb In Workbooks
'如果在密码则另存,密码为空
IF Then , , ""
Next Wkb
'恢复提示
= True
End Sub
以上过程遍历所有打开的工作簿,逐一判断是否存在密码保护,如果有密码则以同名文件另存,以空文本为密码。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“清除密码”,那么打开的所有工作簿会瞬间清除密码。
〖语法补充〗:
(1)属性如果指定工作簿有密码保护,则该属性值为 True。
本例文件参见光盘:..\ 第十一章\清除打开工作簿的密码.xlsm
获取工作簿建立时间和最后一次保存时间
〖案例要求〗:获取工作簿建立时间和最后一次保存时间。
〖知识要点〗:BuiltinDocumentProperties
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 报告创建与修改时间()
On Error Resume Next
'则提示创建时间与修改时间
MsgBox "创建时间:" & (11) & Chr(10) _
& "最后修改时间:" & (12), 64, "友情提示"
'如果有错误提表示工簿且未保存
IF Err <> 0 Then MsgBox "本工作簿未保存"
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“报告创建与修改时间”,如果工作簿未保存则产生图所示的提示,如果已保存过则提示其创建时间和最后的修改时间,见图所示:
未保存工作簿之提示 图 提示创建时间与最后修改时间
〖语法补充〗:
(1) 属性返回一个DocumentProperties 集合,该集合表示指定工作簿的所有内置文档属性。其内置属性主要包括文件的标题、创建者、创建时间、修改时间、页数、字数等等。可以修改以下代码罗列出所有内置属性:
Sub 文件内置属性()
Dim Item As Byte, p
Worksheets(1).Activate
For Each p In
Item = Item + 1
Cells(Item, 1).Value =
Next
End Sub
(2)文档的属性区分内置属性和自定义属性。用户可以自定义一个或者多个属性。下例过程自定义三个文件属性,包含三种数据类型:
Sub 自定义文件属性()
With
.Add Name:="文件编号", LinkToContent:=False, _
Type:=msoPropertyTypeNumber, Value:=1234
.Add Name:="分类", LinkToContent:=False, _
Type:=msoPropertyTypeString, Value:="机密"
.Add Name:="更新日期", LinkToContent:=False, _
Type:=msoPropertyTypeDate, Value:=Date
End With
End Sub
以上过程定义了三个文件属性,包括编号、分类与更新日期。如果该Name已经存在则无法定义,必须改名。所以以上过程执行第二次时一定会产生错误而中断。
Sub 读取属性()
With
MsgBox "文件编号:" & .Item("文件编号") & Chr(10) _
& "文件分类:" & .Item("分类") & Chr(10) _
& "更新日期:" & .Item("更新日期"), 64, "文档属性"
End With
End Sub
如果读取的属性未定义,也会运行时错误而中断程序。执行以上过程后结果如下:
图 获取自定义的文件属性
本例文件参见光盘:..\ 第十一章\创建与保存时间.xlsm
记录文件打开次数
〖案例要求〗:记录文件打开次数。
〖知识要点〗:CustomDocumentProperties
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub Auto_open()
'防错
On Error Resume Next
Dim Open_Count As Long
With
'获取上次打开的次数
Open_Count = .Item("打开次数").Value
'如果有错误
If Err <> 0 Then
'添加新的属性,其值为1
.Add Name:="打开次数", LinkToContent:=False, Type:=msoPropertyTypeNumber, Value:=1
Else
'否则文件属性累加1
.Item("打开次数") = .Item("打开次数").Value + 1
End If
'报各次数
MsgBox "本文件打开次数:" & .Item("打开次数").Value
End With
End Sub
以上过程利用Auto_open做为文件名,表示工作簿每次开启时都执行过程。
在执行过程时,会读取自定义属性的值,如果不存在则新加值为的“打开次数”属性,否则在原值基础上累加1。
(3)保存并关闭工作簿,当重新开启工作簿后,会弹出文件开启次数的对话框,多次开次后会累加该值,见图所示。
图 报告文件开启次数
〖语法补充〗:
(1)属性返回或设置一个DocumentProperties集合,该集合表示指定工作簿的所有自定义文档属性。
(2)通过以下过程可以获取所有自定义属性的名称及值
Sub 获取自定义属性()
Dim Item As Byte
For Each p In
Item = Item + 1
Cells(Item, 1).Value =
Cells(Item, 2).Value =
Next
End Sub
本例文件参见光盘:..\ 第十一章\记录文件打开次数.xlsm
切换图形对象隐藏与显示
〖案例要求〗:切换图形对象的显示状态,如果处于隐藏状态则显示,如果处于显示状态则隐藏,可以反复切换。
〖知识要点〗:DisplayDrawingObjects
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 控制工作簿中图片的显示与否()
'如果当前状态为显示则隐藏图形对象,否则显示图形对象
= IIF( = xlHide, xlPlaceholders, xlHide)
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“DisplayDrawingObjects”,如果当前工作簿的当前图形对象处于隐藏状态,那么会显示出所有插入的图形对象,包括图片、形状、嵌入式图表、艺术字、ActiveX控件、文本框等等;
(4)再次执行宏,当前工作簿的所有工作表中的图形对象都会隐藏起来,而且插入图形、艺术字等等按钮都显示灰色状态,无法使用,见图所示:
图 隐藏图形状态下【插入】功能区部分按钮呈灰色
〖语法补充〗:
(1)属性返回或设置形状的显示方式。它包括三种显示方式,可以利用内置的常量来控制,表11-41即DisplayDrawingObjects属性的常量列表
表11-41 DisplayDrawingObjects属性的常量列表
常量
说明
xlDisplayShapes
显示所有形状
xlPlaceholders
仅显示占位符
xlHide
显示所有形状
(2)DisplayDrawingObjects属性对当前工作簿所有工作表生效,但重启Excel后会恢复为默认值。所以如果希望永远按自己设置的方式控制图形,那么可以设计一个加载宏,配合应用程序级别的事件使代码都所有工作簿且任何时候都生效。
本例文件参见光盘:..\ 第十一章\切换图形对象显示状态.xlsm
设计一个查看一次即自动删除的工作簿
〖案例要求〗:设计一个查看一次即自动删除的工作簿,关闭文件时会自我销毁,且不经过回收站。
〖知识要点〗:Saved、ChangeFileAccess、FullName
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub Auto_Close() '关闭工作簿时自动执行
With ThisWorkbook
'标识为已保存状态
.Saved = True
'设为只读模式
.ChangeFileAccess Mode:=xlReadOnly
'删除工作簿
Kill .FullName
'关闭且不保存
.Close False
End With
End Sub
以上过程会在工作簿关闭时自我删除,请测试代码前备份文件。
(3)保存工作簿,然后再另存一次。因为工作簿关闭时会自我删除,所以必须存一次再另存一次,那么第二次另存的文件会自我删除,第一次保存的文件却存在,那么该文件即为只能查看一次就会自我删除的文件。
(4)将工作簿复制到其它电脑中,如果该电脑中的Excel可以执行宏,那么在查看工作簿一次,关闭时工作簿会自动消失,回收站也找不回来。
〖语法补充〗:
(1)属性用于控件工作簿的保存状态,可读也可写。如果仅仅需要将工作簿标识为已保存状态,却并非事实上对它进行保存,那么可以直接赋值为True,那么关闭工作簿时将不会循询问用户是否保存工作簿的修改。
(2)方法用于更改工作簿的访问权限。它的语法如下:
(Mode, WritePassword, Notify)
其第一参数Mode表示工作簿的访问模式,如果赋值为xlReadOnly则表示只读状态;如果赋值为xlReadWrite则表示可读写状态。
(3)属性表示工作表的全称,包括其磁盘路径与工作簿名。相当于Path与Name之综合体。
本例文件参见光盘:..\ 第十一章\只能查看一次即自我删除.xlsm
禁止插入新工作表
〖案例要求〗:禁止当前工作簿中插入新工作表,即保护工作簿的结构。
〖知识要点〗:ProtectStructure、Protect
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 禁止插入新表()
'如果已经保护工作簿中的工作表次序,那么退出
IF Then
MsgBox "已经处于保护状态", 64
Exit Sub
End IF
'保扩工作表次序,禁止插入及删除,密码为123
Structure:=True, Password:="123"
End Sub
以上过程对工作簿的结构进行保护,从而禁止插入工作表。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“禁止插入新表”,程序会保护当前工作簿的工作表结构,禁止添加、删除工作表。当在工作表标签处单击右键时,【插入】、【删除】、【命名】、【工作表标签颜色】等等与工作表相关的操作菜单都呈灰色禁用状态,同时功能区【审阅】\【保所工作簿】\【保护结构和窗口】也呈勾选状态。如果需要取消限制,可以单击菜单【保护结构与窗口】,并输入密码123。
图 插入、删除工作表的菜单呈灰色 图 保护结构与窗口菜单呈勾选状态
〖语法补充〗:
(1)属性表示工作簿中的工作表次序是否被保护,如果工作簿中的工作表次序处于保护状态,则该属性值为 True。
(2)方法保护工作簿使其不被修改。具体语法如下:
(Password, Structure, Windows)
其中三个可选参数的含义见表11-42:
表11-42 方法参数详解
名称
描述
Password
一个字符串,该字符串为工作表或工作簿指定区分大小写的密码。如果省略此参数,不用密码就可以取消对工作表或工作簿的保护
Structure
如果为 True,则保护工作簿结构(工作表的相对位置)。默认值是 False
Windows
如果为 True,则保护工作簿窗口。如果省略此参数,则窗口不受保护
本例文件参见光盘:..\ 第十一章\禁止插入工作表.xlsm
不打开工作簿而提取数据
〖案例要求〗:从关闭的工作簿中获取数据,工作簿的路径、工作表名名、区域等等可选。假设当前工作簿同路径下有一个文件夹名为“人事资料”,其中有一个工作簿“人事报表.xls”,现要求不打开工作簿的前提下获取其中任何工作表任何区域的值。
〖知识要点〗:Path
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
'声明一个带有四个参数的Sub过程
Sub 取值(路径 As String, 文件 As String, 工作表, 单元格 As String)
'将目标区域复制到以当前表活动单元格为左上角的相同大小的区域中
With (Range(单元格), , Range(单元格), )
'在指定区域输入公式,该公式引用指定路径下的工作表数据,可以是单元格也可以是区域
.FormulaArray = "='" & 路径 & "\[" & 文件 & "]" & 工作表 & "'!" & 单元格
'将公式转换成值
.Value = .Value
End With
End Sub
以上过程带有四个参数,分别为路径、工作簿名、工作表名和区域地址,表示从该处获取数据。程序通过公式将目标工作簿中指定区域的值引入当前表中,数据存放区域为活动单元格为基准向右、向下扩展至与目标区域相同大小的区域。
Sub 不打开工作簿而获取其值1()
'调用Sub过程,指定参数
取值 & "\人事资料", "人事报表.xls", "部门人员统计", "A1:B9"
End Sub
获取指定工作表的值,路径为代码所在工作簿相同路径下一层名为“人事资料”的文件夹,工作簿名称为“人事报表.xls”,工作表名为“部门人员统计”,区域为“A1:B9”。用户也可以根据需要,调用任意文件夹中的工作表数据,但是必须注意不能缺少路径中的“\”
Sub 不打开工作簿而获取其值2()
'调用Sub过程,指定参数
取值 & "\人事资料", "人事报表.xls", "人事资料", "A1:C42"
End Sub
再取获取同工作簿中另一个工作表的值.
(3)返回工作表,激活单元格XFD2,然后使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“不打开工作簿而获取其值1”,程序立即弹出图所示对话框。因为XFD是工作表中最一列,而目标区域有两列,所以无法存放数据;
(4)激活单元格A2,再执行程序“不打开工作簿而获取其值1”,程序会将“人事资料[人事报表.xls]部门人员统计'!A1:B9”中的值导入到活动单元格区域,见图所示:
图 区域无法存放目标数据时产生提示 图 不打开工作簿而引用数据
(5)再次执行过程“不打开工作簿而获取其值2”,那么“人事资料[人事报表.xls] 人事资料'!A1:C42”中的值导入到活动单元格区域中。
〖语法补充〗:
(1)本例输入过程“取值”的所有参数都是文本字符串,不能使用其它数据类型。例如第四参数使用“[a1:b2]”会导致程序错误。同时工作簿的路径与工作簿之间之间必须有一个“\”,否则也会产生错误。
(2)本例使用等号引用目标工作簿的值,那么目标区域中若有空白单元格,当前工作表会以零值显示。
本例文件参见光盘:..\ 第十一章\不打开工作簿而获取其数据.xlsm
合并指定文件夹下每个工作簿中三月生产表到一个工作簿
〖案例要求〗:将指定文件夹中所有工作簿中名为“三月”的工作表合并到一个工作簿中。即文件夹中有多个以“车间”命名的报表,每个车间的报表工作簿中分别有“一月”、“二月”、“三月”等等工作表,现需利用VBA实现所有工作簿中的“三月”工作表合并到一个新工作簿中,合并后该工作簿中的工作表数量等于原有工作簿数量,且每个工作表正好对应于现有车间报表中的“三月”报表。如果延伸一下,可以实现原来按车间命名的报表修改为按月份进行分类,即每个车间的报表包含多个月份的产量,程序可以将它修改为每份报表按月份命名,每个月报表中包括所有车间的产量。
图 待合并的工作簿 图 待合并工作簿中的工作表
〖知识要点〗:SaveAs、Add、Close
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
'声明一个带有四个参数的Sub过程
Sub 取值(路径 As String, 文件 As String, 工作表, 单元格 As String)
'将目标区域复制到当前月相同大小的区域中
With Range(单元格)
'在指定区域输入公式,该公式引用指定路径下的工作表数据,可以是单元格也可以是区域
.FormulaArray = "='" & 路径 & "\[" & 文件 & "]" & 工作表 & "'!" & 单元格
'将公式转换成值
.Value = .Value
End With
End Sub
以上过程用于将指定目录下的工作簿中指定工作表数据复制到当前表,仅仅复制数据,不包括格式。
Sub 合并三月报表到一个工作簿()
'关闭屏幕刷新,加快速度
= False
Dim FolderName As String, 工作簿名 As String, Arr() As String, 工作簿数量 As Integer, Item As Integer
'创建文件夹中工作簿列表,将所有工作表簿名导入数组Arr中
工作簿名 = Dir("D:\生产表\*.xls")
While 工作簿名 <> ""
工作簿数量 = 工作簿数量 + 1
ReDim Preserve Arr(1 To 工作簿数量)
Arr(工作簿数量) = 工作簿名
工作簿名 = Dir
Wend
'如果没有工作簿则退出程序
IF 工作簿数量 = 0 Then Exit Sub
'添加一个新工作簿,用于存放所有车间的三月产量
'如果新工作簿中工作表数量小于待合并的工作簿数量则新建工作表,使其个等于工作簿数量
IF < 工作簿数量 Then
, , 工作簿数量 -
End IF
'遍历工作表
For Item = 1 To
'将工作表名修改为对应的工作簿名(车间名)
Sheets(Item).Name = Replace(Arr(Item), ".xls", "")
Sheets(Item).Select
'引用该工作簿中“三月”工作表A1:B10的数据
取值 "D:\生产表", Arr(Item), "三月", "A1:B10"
Next Item
= True
'最后合并完成的工作簿包括所有车间名为三月的数据总和,
'合并后的新工作簿中每个表以原有的工作簿名命名
End Sub
以上过程首先统计“D:\生产表”文件夹中有多少个工作簿,然后新建一个工作簿,将根据文件夹中工作簿的数量来决定新工作簿中的工作表数量,且所有工作表以待合并工作簿的名称命名。
最后利用循环将每个工作簿中的数据复制到新工作簿对应的工作表中。执行本过程请确保D盘中有相应的文件夹和工作簿。可以将光盘中的文件复制到D盘或者直接修改代码中的路径两种方式确保代码正确执行。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“合并三月报表到一个工作簿”,那么“D:\生产表”文件夹下所有工作簿中名为三月的报表合并到新工作簿。合并所有工作簿中包括所有车间的三月产量表,见图所示。
图 合并后的三月产量表
(4)如果需要将四个车间报表中一月、二月、三月的产量全部重新合并为三个报表,分别为所有车间一月产量、二月产量、三月产量,那么可以修改代码,再加入一次循环来完成。即按车间分类的产量表修改为按月份分类:
Sub 合并每个月报表为独立工作簿()
= False
Dim FolderName As String, 工作簿名 As String, Arr() As String, 工作簿数量 As Integer, Item As Integer, Months As Byte
工作簿名 = Dir("D:\生产表\*.xls")
While 工作簿名 <> ""
工作簿数量 = 工作簿数量 + 1
ReDim Preserve Arr(1 To 工作簿数量)
Arr(工作簿数量) = 工作簿名
工作簿名 = Dir
Wend
On Error Resume Next
'在D盘“生产表”文件夹中建立一个“按月份分类”的文件夹,用于存放重新分类的工作簿
MkDir "D:\生产表\按月份分类"
IF 工作簿数量 = 0 Then Exit Sub
'循环三次,别取出三个月的车间产量表
For Months = 1 To 3
IF < 工作簿数量 Then
, , 工作簿数量 -
End IF
For Item = 1 To
Sheets(Item).Name = Replace(Arr(Item), ".xls", "")
Sheets(Item).Select
取值 "D:\生产表", Arr(Item), (Months, "[DBNum1]0月"), "A1:B10"
Next Item
'将新工作簿存入新建文件夹中
"D:\生产表\按月份分类\" & (Months, "[DBNum1]0月")
'关闭工作簿
Next Months
= True
'最后的结果为将按车间分类,每个工作簿包含三个月产量的工作簿重新分类为按月份分类,每个工用簿包含四个车间的产量
End Sub
执行以上过程可以实现“D:\生产表”中创建一个“按月份分类”的文件夹,然后将原有四个车间的报表重新合并为按月份分类的报表。图是一月份四个车间的产量表,图是程序生成的新文件夹及三个新产量表。
图 所有车间一月产量表 图 将原报表按月份分类
〖语法补充〗:
(1)本例是复制所有车间产量表中的数据,那么可以通过等号来取值。而且在预先知道每个工作表中已用区域的前提下进行,否则不能获取区域地址。如果需在让程序自自己决定复制哪个区域,而且将工作表中的格式设置也复制到新工作簿,那么只能打开工作簿才后做到。本书的最后一章设计Excel百宝箱插件将会详述设计思路,及提供详细代码。
本例文件参见光盘:..\ 第十一章\合并指定文件夹下每个工作簿中三月生产表到一个工作簿.xlsm
建立指定文件夹下所工作簿目录和工作表目录
〖案例要求〗:对指定主目录下所有工作簿中所有工作表建立目录,工作簿目录在A列,工作表目录在B列。
〖知识要点〗:Name、Open、Close
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 建立所有工作簿中的工作表目录()
= False
Dim fd As FileDialog, path As String, Old_Name As String
'弹出对话框,让用户选择文件夹
Set fd = (msoFileDialogFolderPicker)
'如果选择了文件夹则记录路径
IF = -1 Then
path = (1) & IIF(Right((1), 1) = "\", "", "\")
Else
Exit Sub
End IF
'记录当前工作簿名
Old_Name =
Dim 工作簿名 As String, Arr() As String, 工作簿数量 As Integer, Item As Integer, j As Integer, k As Integer
'将工作表簿名存入数组
工作簿名 = Dir(path & "*.xls*")
While 工作簿名 <> ""
工作簿数量 = 工作簿数量 + 1
ReDim Preserve Arr(1 To 工作簿数量)
Arr(工作簿数量) = 工作簿名
工作簿名 = Dir
Wend
IF 工作簿数量 = 0 Then Exit Sub
j = 1
For k = 1 To 工作簿数量
j = j + 1
'记录工作簿名称到A列
Workbooks(Old_Name).Sheets(1).Cells(j, 1) = Arr(k)
'逐个打开工作簿
path & Arr(工作簿数量)
'在所有工作表循环
For Item = 1 To
j = j + 1
'记录所有工作表名称到B列
Workbooks(Old_Name).Sheets(1).Cells(j, 2) = Sheets(Item).Name
Next Item
'关闭工作簿
(False)
Next
= True
End Sub
以上过程首先弹出一个文件夹“浏览”对话框,让用户选择一个文件夹,程序将取出其中所有Exeel文件的工作表目录。注意“浏览”对话框是用于获取文件夹目录,无法看到文件的。
然后利用DIR函数统计其中文件个数,如果数量为0则退出程序,否则记录所有文件名。
最后逐个打开工作簿,再逐一循环所有工作表,将工作簿名存入A列,工作表名存入B列。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“建立所有工作簿中的工作表目录”,程序将弹出图所示对框。当选择文件夹并单击“确定”按钮后,根据文件夹中工作簿的多少会花费不同的时间建立目录。图是“生产表”目录下四个工作簿的所有工作表目录:
图 浏览文件夹 图 工作簿与工作表目录
〖语法补充〗:
(1)用户浏览目录夹时,如果选择了文件夹,那么路径最后右边没有“\”;如果选择磁盘根目录,那么在路径右边有“\”。所以在程序中需要利用IF配合Right来判断路径中是否有“\“,这点很重要。
(2)Dir函数可以表示一个文件名、目录名或文件夹名称,它必须与指定的模式或文件属性、或磁盘卷标相匹配。通常用它来判断文件、目录是否存在或者文件路径是否规范等等。在本书第18章将详述其语法与功能。
本例文件参见光盘:..\ 第十一章\建立所有工作簿中的工作表目录.xlsm
断开与其它工作簿的数据链接
〖案例要求〗:断开与其它工作簿的数据链接,即部分单元格引用了其它工作簿的数据,为了防止被引用的工作簿删除后当前表数据产生错误,利用VBA将当前工作簿所有工作表的外部引用转换成值。
〖知识要点〗:BreakLink
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 断开与其它工作簿的数据链接()
'将所有外部链接转换成值
Name:=(Type:=xlLinkTypeExcelLinks)(1), Type:=xlLinkTypeExcelLinks
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“断开与其它工作簿的数据链接”,那么当前工作簿所有工作表的外部引用都会转换成值。
〖语法补充〗:
(1)方法可将将链接到其它工作簿或者OLE 源的公式转换为值。语法如下:
(Name, Type)
第一参数表示链接的名称,第二参数表示链接类型,当类型为xlLinkTypeExcelLinks时表示Excel 工作表的链接。
本例文件参见光盘:..\ 第十一章\断开与其它工作簿的数据链接.xlsm
Windows 对象案例
Windows对象是Excel中所有Window(窗口)对象的集合。本节对窗口的应用进行演示。
获取窗口列表
〖案例要求〗:获取当前已打开的窗口。
〖知识要点〗:Caption
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 遍历窗口()
'声明一个窗口型变量
Dim win As Window, Item As Byte
'遍历所有窗口
For Each win In Windows
'累加变量
Item = Item + 1
'将窗口标题导入单元格
Cells(Item, 1) =
Next win
End Sub
以上过程遍历所有窗口,将逐个提取窗口的标题,再导入到工作表A列。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“遍历窗口”,在活动工作表中A列将罗列出所有窗口的标题,见图所示:
图 获取窗口列表
〖语法补充〗:
(1)属性它代表文档窗口标题栏中显示的名称。如果工作表已保存,则标题中包含后缀名,否则不包含后缀名,如图中“Book3”。
本例文件参见光盘:..\ 第十一章\遍历窗口.xlsm
确保随时打开工作簿都窗口最大化
〖案例要求〗:对工作表密码保护,关闭时缩小窗口,重启后需要密码确认查看权限,如果密码正常则放大窗口,否则窗口最小化,无法查看数据。
〖知识要点〗:WindowState
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub Auto_open()
'如果有密码保护则退出程序
IF Then Exit Sub
'将窗口最大化
Windows(1).WindowState = xlMaximized
End Sub
(3)保存并关闭工作簿,重新启动工作簿后,它总会窗口最大化。除非已对工作簿的窗口进行密码保护。
〖语法补充〗:
(1)属性返回或设置窗口的状态。包括以下三种状态:
表11-43 窗口状态列表
名称
值
描述
xlMaximized
-4137
最大化
xlMinimized
-4140
最小化
xlNormal
-4143
正常
本例文件参见光盘:..\ 第十一章\确保每次打开都最大化.xlsm
切换当前窗口的网格线、滚动条、标题与工作表标签
〖案例要求〗:切换当前窗口的网格线、滚动条、标题与工作表标签,运行时可以隐藏,再次运行时则显示。
〖知识要点〗: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 With
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“切换”,当前窗口中的网格线、滚动条、行列标题与工作表标签将会在显示与隐藏之间切换。
〖语法补充〗:
(1)属性表示网格线的显示状态。
(2)属性表示水平滚动条的显示状态。
(3)属性表示垂直滚动条的显示状态。
(4)属性表示行标题和列标题的显示状态。
(5)属性表示工作簿标签的显示状态。
本例文件参见光盘:..\ 第十一章\切换.xlsm
自由滚动窗口方便阅读工作表数据
〖案例要求〗:当前工作表数据超过1屏,利及VBA实现每秒钟向下滚动1行,方便用户阅读;如果滚动到最后一行,那么再单执行则变成每秒钟向上滚动一行。
〖知识要点〗:SmallScroll、VisibleRange
〖实现步骤〗:
(1)激活需要滚动的工作表,单击菜单【开发工具】\【插入】\【命令按钮(ActiveX控件)】,并在工作表中按下左键拖动,分别绘制两个命令按钮;
(2)单击右键,从菜单中选择【查看代码】,从而工作表事件代码窗口;
(3)在代码窗口输入以下代码:
Dim a
Sub 表格每秒滚动一行()
Dim i As Integer, Time_one As Single, Time_two As Single
Time_one = Timer: Time_two = Timer
Do
Time_one = Timer()
(1)
IF (Abs(Time_two - Time_one) > 1) Then
Time_two = Time_one
DOWN:=a
i = i + 1
DoEvents
End IF
Loop Until i = Cells(, 1).End(xlUp).Row - 2
= (a = 1, "向上滚动", "向下滚动")
End Sub
以上过程让当前窗口每秒钟滚动一行,而滚动的方向由变量a决定。
Private Sub CommandButton1_Click()
'如果已用区域不超过一屏显示则不执行程序
IF Cells(, 1).End(xlUp).Row < + Then Exit Sub
'在B3处锁定窗格,让两行行标题固定在上方
= False
Range("A3").Select
= True
'如果按钮标题是向下滚动则变量a赋值1,并激活A3,开始向下滚动表格
IF ( = "向下滚动") Then
a = 1
[a3].Select
= "向上滚动"
表格每秒滚动一行
Else
'否则直接滚动到最后一行,且该行显示在当前屏幕最底端
DOWN:=-(( + - 1) - ( + Cells(, 1).End(xlUp).Row))
a = -1
= "向下滚动"
表格每秒滚动一行
End IF
End Sub
以上过程是单击第一个按钮时执行的过程,它根据按钮的显示标题决定变量a的值,并改变按钮的标题。当变量a产生变化后,窗口滚动的方向也相应的变化。
Private Sub CommandButton2_Click()
= "暂停"
End
End Sub
以上过程用于暂停窗口滚动。
(4)返回工作表,单击功能区菜单【开发工具】\【设计模式】,从而退出设计模式;
(5)单击第一个按钮执行程序,如果当前工作表数据不超过一屏,则无任何反应;如果当前工作表数据超过一屏,那么程序会首先定位于最后一行,然后每秒钟向上滚动一行,中途可以单击第二个按钮中断程序;
(6)当滚动到最顶端后,滚动会自动停止,且按钮的标题已自动修改为“向下滚动”。单击“向下滚动”按钮,窗口会每秒钟向下滚动一行,直到最后一个非空行。
〖语法补充〗:
(1)方法表示按行或按列滚动窗口内容。它的具体语法如下:
表达式.SmallScroll(Down, Up, ToRight, ToLeft)
它的四个可选参数含义如下:
表11-44 方法参数列表
名称
描述
Down
将内容向下滚动的行数
Up
将内容向上滚动的行数
ToRight
将内容向右滚动的列数
ToLeft
将内容向左滚动的列数
其实也可以仅仅使用Down和ToRight两参数,因为可以对其赋负值来替代其它两个参数的功能。例如向下滚动2行可以下语句:
DOWN:=2
若向上滚动10行,仍然可以使用DOWN参数,改用负值即可:
DOWN:=-10
(2)属性用于返回一个Range对象,它代表显示在窗口或窗格中的单元格区域。即在当前窗口中能看到的区域,包括能看到部分的单元格。例如图中第8行和第E列虽然仅仅能看到部分,它也属于VisibleRange区域。使用代码“MsgBox ”可以得到地址“$A$1:$E8$”:
图 利用VisibleRange属性获取可见区域地址
(3)属性用于控制窗格的冻结状态。如果窗格被冻结,则该属性值为True。
本例文件参见光盘:..\ 第十一章\每秒钟滚动一行.xlsm
以当前单元格为基准拆分窗格
〖案例要求〗:以当前单元格为基准拆分窗格。
〖知识要点〗:SplitColumn、SplitRow
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 以当前单元格为基准拆分窗格()
With ActiveWindow
.SplitColumn = - 1
.SplitRow = - 1
End With
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“以当前单元格为基准拆分窗格”,当前窗口会以活动单元格为基准,将窗口拆分成四个。见图所示:
图 以当前单元格为基准拆分窗格
〖语法补充〗:
(1)属性返回或设置将指定窗口拆分成窗格处的列号(拆分线左侧的列数)。
(2)属性返回或设置将指定窗口拆分成窗格处的行号(拆分线以上的行数)。
本例文件参见光盘:..\ 第十一章\以当前单元格为基准拆分窗格.xlsm
计算活动单元格左边距
〖案例要求〗:计算活动单元格的左边距,是指活动单元格到左边行标题的距离。代码需要通用于所有情况,包括冻结窗格时。
〖知识要点〗:Panes
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 计算当前单元格左边距()
With
'循环所有窗格(当冻结时有多个窗格)
For i = 1 To .Count
'如果活动单元格与第 i个窗格存在交集
IF Not Intersect(ActiveCell, .Item(i).VisibleRange) Is Nothing Then
'如果是第一个窗格或者第三个窗格
IF i = 1 Or i = 3 Then
'结果为活动单元格的左边距减去当前窗格第一个单元格的左边距
MsgBox - .Item(i).VisibleRange(1, 1).Left
Else
'结果第一个窗格的宽度加上活动单元格的左边距加减去当前窗格第一个单元格的左边距
MsgBox .Item(1). + - .Item(i).VisibleRange(1, 1).Left
End IF
Exit For
End IF
Next
End With
End Sub
虽然可以计算单元格的左边距,然而它的BUG很多,它将隐藏单元格的宽度也计算进去,从而差生误差。本例采用Panes配合VisibleRange可以避免这个BUG。
以上过程对活动单元格在第一、第三窗格时采用一种计算方式,对活动单元格在第二、第四窗格时采用另一种计算方式。前者利用活动单元格的左边距(Left)减去窗格第一个单元格的左边距来实现忽略隐藏列的宽度;后者则在前者的计算基上加上第一个窗格中可见区域的宽度。
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“计算当前单元格左边距”,假设在工作表中窗格显示方式如图所示,那么计算结果为,而直接用“”计算则结果结果为,两者的差异正好是A列和E列两个隐藏列的宽度。
图 计算活动单元格左边距
〖语法补充〗:
(1)属性返回一个Panes集合,该集合表示指定窗口中的所有窗格。
(2)如果窗口没有冻结时,窗口中只有一个窗格;如果冻结,那么可能有四个窗格,也可能有两个窗格,由冻结的位置而定。
本例文件参见光盘:..\ 第十一章\以当前单元格为基准拆分窗格.xlsm
计算活动单元格的屏幕位置
〖案例要求〗:单元格的Left属性与Top属性是相对于工作表左边缘、上边缘的距离,以磅为单位。不管Excel的窗口如何移动、功能的位置如何变化都不影响该置。本例中演示单元格相对于屏幕的象素位置。
〖知识要点〗:PointsToScreenPixelsX
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 屏幕位置()
'如果选择单元格
IF TypeName(Selection) = "Range" Then
With ActiveWindow
'将点)为单位转换为以屏幕像素(屏幕坐标)为单位
MsgBox "上边距:" & .PointsToScreenPixelsX(.) & Chr(10) _
& "左边距:" & .PointsToScreenPixelsY(.), 64, "屏幕座标"
End With
End IF
End Sub
(3)返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“屏幕位置”,如果当前选择了单元格,那么程序会弹出图所示的提示,表示活动单元格的屏幕位置。
选择同一个单元格时,其Top和Left总是不变化,不管如何移动或者缩放窗口,但其屏幕象素位置却会变化。
图 计算活动单元格的屏幕位置
〖语法补充〗:
(1)方法可将横向度量值由以点(文档坐标)为单位转换为以屏幕像素(屏幕坐标)为单位。返回转变后的度量值。
本例文件参见光盘:..\ 第十一章\计算活动单元格的屏幕位置.xlsm
三种方式不显示零值
〖案例要求〗:使用三种方式不显示零值,包括隐藏活动工作表中的所有零值、隐藏选区零值及删除所有零值。
〖知识要点〗:DisplayZeros
〖实现步骤〗:
(1)单击菜单【插入】\【模块】;
(2)在模块代码窗口输入以下代码:
Sub 不显示零值()
Dim Opt As Byte
On Error Resume Next
Star:
'让用户选择零值的处理方式
Opt = ("输入1:隐藏所有零值单元格" & Chr(10) & _
"输入2:隐藏选区零值" & Chr(10) & _
"输入3:删除所有零值", "隐藏零值", 1, , , , , 1)
'超过Byte的范围则返回重新输入
IF Err <> 0 Then GoTo Star
Select Case Opt
Case 1 '输入1就隐藏当前表所有零值
= False
Case 2 '输入2则隐藏选区中的零值
IF TypeName(Selection) = "Range" Then
= "[=0]"""";G/通用格式"
Else
MsgBox "请选择区域。", 64, "提示"
End IF
Case 3 '输入3则将零值替换成空白
0, "", xlWhole
End Select
End Sub
(3)选回工作表,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行“不显示零值”,将弹出图所示对话框,让用户选择对零值的处理方式。如果选择1,那么当前表所有零值都会隐藏起来(编辑栏中可以看到,单元格中不显示),见图所示;如果选择2,那么只隐藏选择区域的零值;如果选择3则删除所有当前工作表中的零值,单元格中和编辑栏中都不显示,见图所示:
图 选择零值处下方式 图 隐藏零值 图 删除零值
〖语法补充〗:
(1)属性表示零值的显示方式,则为属性值True则显示零值,否则隐藏零值,它的设置仅对当前工作表生效,其它工作簿或者工作表需要再设置。
(2)本例中的三种方式各有优势。第一种方式对整个工作表生效,仅改变显示值,不改变实际值,随时可以撤消隐藏设置;第二种方法对可以对选定的任意区域设置隐藏,而且也方便取消零值的隐藏,但如果选择已有部分单元格有自定义数字格式将破坏原有设置;第三种方式删除所有零值单元格,无法还原。虽然可以定位空单元格,再输入“0”值,然而却可以将原来的空单元格也意外的加入“0”。
本例文件参见光盘:..\ 第十一章\三种方法不显示零值.xlsm
第十二章
Excel的事件应用案例
Excel的事件可以让程序自动执行,或者基于某个条件下自动执行,不需要用户手工引导,这使事件过程在工作中得到了广泛的应用。
在本书第八章中介绍了事件的基本知识,然后通过第九章、十章和十一章地学习,已经常握代码编写规则、常见语句的语法,以及对Excel的对象应用有了较深入地认识,那么现在足以编写Excel事件的实例应用程序了。本章对Excel事件将进行全面地高级应用案例演示,让读者加深对事件的理解及提升对象及事件的驾驭能力。
本章要点:
应用程序事件案例
工作簿事件案例
工作表事件案例
ActiveX控件事件案例
应用程序事件案例
应用程序级别的事件对所有工作簿、工作表和单元格都生效。编写应用程序级别事件时需要用到类模块的知识。
新工作簿环境设计
〖案例要求〗:每次新建工作簿时,默认有7个工作表,第一个工作表名为“总表”,其它表名为分表。
〖实现步骤〗:
(1)新建工作簿,使用快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【模块】;
(3)在模块中录入以下代码:
Dim xlapp As New appevents '声明一个对象
Sub auto_open() '工作表开启时执行
Set = Application '将应用程序赋与对象变量
End Sub
Sub auto_close()
Set = 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 Byte
, , 7 - '创建4个工作表(默认有3个)
(1).Name = "总表" '将第一个命名为总表
For i = 2 To '从第二开始 直到最后一个
(i).Name = "分表" & i - 1 '改名为“分表”加编号
Next
End Sub
在本过程的代码中,首先通过“”计算新工作簿的默认工作表数量,然后通过“”添加相应的新工作表,使其加上原来的工作表数量后等于7。最后再对每个工作表重命名。其中事件过程的参数Wb代码新建的工作簿对象。
(7)关闭VBE界面返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,执行“Auto_open”宏;
(8)单击按钮新建一个工作簿,可以发现它将带有7个工作表,且命名完全按照程序的思路进行。见图所示:
图 利用应用程序事件设置新工作簿环境
本例文件参见光盘:..\ 第十二章\利用事件设置新工作簿的工作表.xlsm
应用程序的事件过程需要用到类模块相关的代码。关于类的知识将在本书第25章进行介绍,读者也可以学习第25章后再继续应用程序级别事件的编写。不过在应用程序事件过程涉及的类的知识不多,不管其它任何事件,都可以从本例代码中做小小修改即可。
打开任意工作簿时全自动备份
〖案例要求〗:打开任意工作簿时全自动备份,备份文件路径与原文件一致,名称为原名添加备份时间。
〖实现步骤〗:
(1)新建工作簿,使用快捷键【Alt+F11】进入VBE界面;
(2)单击菜单【插入】\【模块】;
(3)在模块中录入以下代码:
Dim xlapp As New appevents '声明一个对象
Sub auto_open() '工作表开启时执行
Set = Application '将应用程序赋与对象变量
End Sub
Sub auto_close()
Set = Nothing '释放变量
End Sub
(4)单击菜单【插入】\【类模块】;
(5)单击快捷键【F4】打开属性窗口,将类模块的默认名“类1”修改为“appevents”;
(6)在类模块中输入以下代码:
Public WithEvents app As Application '声明可触发事件的对象变量
'开启工作簿时在原路径下备份,备份文件为原加加备份时间
Private Sub app_WorkbookOpen(ByVal Wb As Workbook)
& "\备份(" & Format(Now(), "hhmmss)") &
End Sub
本过程可以实现打开任意工作簿时将文件备份。
(7)关闭VBE界面返回工作表,使用快捷键【Alt+F8】打开“宏”对话框,执行“Auto_open”宏;
(8)打开一个工作簿,假设为C盘下“生产表.xlsx”,那么该工作簿开启后在C盘中会立即产生该文件的备份,如果修改错误,且因为步骤太多无法撤消操作或者停电等等意外原因破坏文件,那么可以从备份文件中还原所有数据。
图 开启工作簿时自动备份
本例文件参见光盘:..\ 第十二章\开启任意工作簿时备份.xlsm
工作簿事件案例
工作簿级别的事件对当前工作簿中所有工作表和单元格都生效,对其它工作簿不产生任何影响。
新建工作表时自动设置页眉
〖案例要求〗:对当前工作簿中任何工作表添加页眉,页眉内容包括当前日期和工作表当前页、总页数。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在窗口中录入以下工作簿级“Workbook_NewSheet”事件的代码:
Private Sub Workbook_NewSheet(ByVal Sh As Object)
With '设置页面
.LeftHeader = "&D" '页眉左边插入日期
.CenterHeader = "" '中间空白
.RightHeader = "第&P页总&N页" '右边显示页数
End With
End Sub
本代码对所有新建的工作表都生效,在创建工作表时默认生成页眉。在代码中包括设置页眉的左、中、右三个位置的内容。其中事件过程的参数Sh代表新建工作表对象。
(4)以上过程仅仅对新工作表生效,工作簿已经存在的工作表暂无法生成页眉。所以需要再加入以下代码:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
With '设置页面
.LeftHeader = "&D" '页眉左边插入日期
.CenterHeader = "" '中间空白
.RightHeader = "第&P页总&N页" '右边显示页数
End With
End Sub
本事件过程代码表示打印工作前之前设置页眉。其中事件过程的参数Cancel用于控制工作表是否可以打印,如果对其赋值Flase则禁止用户打印工作表数据。
(5)关闭VBE窗口返回工作表界面,将任意工作表进行打印预览,都可以发现其页眉已经自动设置OK。也可以通过功能区【视图】中的【页面布局】来预览设置的页眉,见图所示:
图 利用工作簿事件设置的页眉预览
本例文件参见光盘:..\ 第十二章\自动设置工作表页眉.xlsm
禁止缩小工作簿窗口
〖案例要求〗:工作簿窗口默认是最大化,通过工作簿事件禁止终端用户修改工作簿窗口的大小,永远保持最大化状态。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
Private Sub Workbook_WindowResize(ByVal Wn As Window)
= xlMaximized
MsgBox "禁止缩小工作区窗口", 64, "提示"
End Sub
以上过程在用户改变工作簿窗口大小时执行,代码表示只要改变窗口的大小就将它自动还原为最大化,且提示“禁止缩小工作区窗口”。其中事件过程的参数Wn代表窗口对象。
(4)返工作表界面,单击右上角的“窗口最小化”和“还原窗口”都已经失效,而且弹出图所示的提示信息。
注意是下面的三个按钮控制工作簿窗口的缩放及关闭,上面的三个按钮是应用控制Excel应用程序的。
图 “还原窗口”按钮 图 修改窗口大小时的提示
本例文件参见光盘:..\ 第十二章\禁止修改工作簿窗口大小.xlsm
未汇总则禁止关闭工作簿
〖案例要求〗:工作簿中有三个工作表,两个存放生产数据,一个名为“汇总表”,在“汇总表”的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 IF
End Sub
以上过程在关闭工作簿之前执行,程序会检查汇总数据存放单元格是否空白,如果空白则禁止保存。事件过程的参数Cancel用于指定是否可以保存工作簿,赋值为True时允许保存,赋值为False时禁止保存。
(4)返回工作表界面,在未汇总的状态下单击Excel的关闭按钮,将弹出图所示提示。
图 未汇总则禁止关闭
本例文件参见光盘:..\ 第十二章\未汇总则禁止关闭工作簿.xlsm
新建工作表时以当前时间命名
〖案例要求〗:新建工作表时以当前时间命名,时分秒形式。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
Private Sub Workbook_NewSheet(ByVal Sh As Object)
'对新建的工作表命名,以当前日间格式化成时分秒后做为命称
= Format(Now(), "H时M分S秒")
End Sub
(4)返回工作表界面,新建一个工作表,那么该表的默认名称时当前系统时间,见图所示:
图 新建工作表时以当前时间命名
本例文件参见光盘:..\ 第十二章\新建工作表时以当前时间命名.xlsm
关闭工作簿前删除多余工作表
〖案例要求〗:关闭工作簿前删除所有没有用的空表。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
'声明工作簿事件,在关闭工作簿时执行
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim Item As Integer
'遍历工作表
For Item = To 1 Step -1
'当删除到最后一个工作表时不管是否空表都停止
IF = 1 Then End
'关闭警告提示,避免删除工作表时中断
= False
'如果工作表是空表(没有数据和图形)则删除
IF (Worksheets(Item).Cells) = 0 And Worksheets(Item). = 0 Then
Worksheets(Item).Delete
End IF
'还原提示
= True
Next Item
'保存工作簿并关闭
SaveChanges:=True
End Sub
以上过程从最后一个工作表开始,向第一个工作表进行循环检查,如果既没有数据也没有Shapes对象则删除,但是当只一个工作表时将它保留,因为工作簿不允许没有一个工作表。
(4)保存代码关闭工作簿,当重新打开后可以发现所有未使用过的工作表都已经删除。
本例文件参见光盘:..\ 第十二章\关闭工作簿前删除多余工作表.xlsm
除了月底禁止打印总表
〖案例要求〗:除了本月底最后一天禁止打印总表,其它工作表不限制。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
'声明工作簿事件,打印工作表时触发
Private Sub Workbook_BeforePrint(Cancel As Boolean)
'如果当前表是“总表”
IF = "总表" Then
'如果日期是本月最后一天
IF Date = (Date, 0) Then
'允许打印
Cancel = False
Else
'则否禁止打印
Cancel = True
End IF
End IF
End Sub
以上过程首先检查当前表名是否“总表”,表示不限制其它工作表;然后检查今日日期是否等于本月最后一天,如果是则将工作簿事件过程的参数Cancel赋为False,表示允许打印,否则禁止打印,连预览也不行。
其中工作表函数EoMonth属于Excel 2007的内部集成函数,可以直接使用;但Excel 2003中需要加载分析工具库才可以使用,见图所示。如果加载宏对话框中的列表是空白,没有任何加载宏,那么表示安装时未安装完整,那么可以在代码中做调整。例如代码“(Date, 0)”的功能可以使用以下代码替代,而且它是Excel 2003和Excel 2007通用的:
Day((Year(Date), Month(Date) + 1, 0))
图 加载分析工具库
(4)返回工作表界面,进入“总表”,如果今天不是月末最后一天,那么无法预览或者打印工作表,而其它日期可以打印。
本例文件参见光盘:..\ 第十二章\除了月底禁止打印总表.xlsm
调整窗口大小时报告可见区域行列数
〖案例要求〗:调整窗口大小时报告可见区域行列数。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
'声明工作簿事件,在改变窗口大小时触发
Private Sub Workbook_WindowResize(ByVal Wn As Window)
'获取可见区或的行数与列数
MsgBox "可见行:" & & Chr(10) & _
"可见列:" & , 64, "当前窗口行列数"
End Sub
(4)返回工作表界面,当按下缩小、放大、还原按钮,或者拖动方式改变窗口大小时,Excel会弹出可见区域行列数的提示,见图所示:
图 可见区域行列数
本例文件参见光盘:..\ 第十二章\调整窗口大小时报告可见区域行列数.xlsm
禁止切换到其它工作簿
〖案例要求〗:禁止切换到其它工作簿。
〖实现步骤〗:
(1)按下快捷键【Alt+F11】打开VBE界面;
(2)如果没有显示工程资源管理器则通过快捷键【Ctrl+R】调出,然后双击ThisWorkbook进入工作簿事件代码窗口;
(3)在代码窗口中录入以下工作簿事件过程代码:
'声明工作簿事件,激活当前工作簿以外的窗口时触发此事件
Private Sub Workbook_WindowDeactivate(ByVal Wn As Window)
'激活当前工作簿的窗口
End Sub
(4)返回工作表界面,单击快捷键【Ctrl+N】建立新工作簿,但是当前工作簿的窗口永远处于激活状态;
(5)从状态栏单击其它工作簿的窗口,不会产生任何反应,工作簿事件过程已禁止切换到其它工作簿窗口。
本例文件参见光盘:..\ 第十二章\禁止切换到其它工作簿.xlsm
工作表事件案例
工作表事件仅仅对代码所有工作表产生作用。本节对工作表事件进行详细的案例介绍。
选择单元时在状态栏提示地址
〖案例要求〗:选择当前工作表任何区域时在状态栏提示选区地址。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
= "当前选区是:" &
End Sub
以上事件过程在选择任单元格时执行,StatusBar代表状态栏,可以对其赋值,但赋值的字符受长度需要受限,因为状态栏只能显示有一行。事件过程中的参数Target代表选区的单元格对象。
(3)返回工作表界面,选择任意单元格后,在状态栏都会显示其地址。如果选择多个区域也可以将多区域的地址完整显示出来,见图所示。使用工作表事件时不能跨表操作,即同时选择多个工作表的单元格区域。
图 在状态栏显示选区地址
Target和ActiveCell、Selection三个都代表单元格,它们有相同点,也有不同点。相同点是用在“Worksheet_SelectionChange”事件中,且选择单个单元格时,它们三个方法都可以用于表示当前选择的单元格,此时没有任何分别。不同点是:Target是VBA内部的预定义对象变量,只仅仅用于事件过程中,可以用于表示当前选择的区域;而Selection可以用在任何模块中,用于用于表示当前选择的区域(有时也可以表示其它的对象,如图形对象等等);Activecell可以在任何模块中使用,它用于表示活动单元格,而活动单元格仅仅一个,即使选择了多个区域。这代表整个选区的Selection与Target是不同的。
本例文件参见光盘:..\ 第十二章\在状态栏显示选区地址.xlsm
快速录入出勤表
〖案例要求〗:在图所示出勤表的B2:H11区域中单击可以产生“迟到”,双击可以产生“早退”,而右击时产生“请假”,从而提升录入效率。
图 出勤表
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
IF Not (Target(1), Range("B2:H11")) Is Nothing Then
IF = 1 Then Target = "迟到"
End IF
End 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 (Target, Range("B2:H11")) Is Nothing Then
Target = "早退"
End IF
End Sub
以上事件过程用于双击单元格时输入“早退”,同样只对B2:H11区域生效。
(4)继续输入以下事件过程代码:
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
IF Not (Target, Range("B2:H11")) Is Nothing Then
Cancel = True
Target(1) = "请假"
End IF
End Sub
以上事件过程用于右键单击单元格时,在单元格中产生“请假”,仅对B2:H11区域生效。其中“Cancel = True”表示取消右键菜单,而在其它区域单击右键时则可以恢复右键菜单。
(5)返回工作表,并单击B2:H11区域中任意单元格即可实现“标识迟到”的功能,而选择超过1个单元格时则忽略;再在B2:H11区域任意单元格双击、右键单击都可以完成预设的功能。
本实例可以实现快速录入不超过三项的出勤状记录,如果还需要加入病假、年假、公假等等,那么只能通加自定义菜单来完成,参阅本书第23章。
本例文件参见光盘:..\ 第十二章\快速录入出勤表.xlsm
建立只能使用一次的超链接
〖案例要求〗:让工作表中的超级链接只能使用一次,当用户单击链接(即链接的任务完成)后自动消失。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
End Sub
以上事件过程用于在工作表中发生超级链接事件时删除超链接。事件过程中Target代表当前单击的超级链接,利用Delete方法可以清除超链接。
(3)反回工作表中,在A1单元格录入一个网址,Excel会自动为其加上超级链接功能,见图所示;
(4)单击A1单元格,此时可以打开百度网页,而A1单元格的链接瞬间被删除,链接的任务已经完成。
图 网址链接
本例文件参见光盘:..\ 第十二章\建立任务完成后自动删除的超链接.xlsm
让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则呈现星期加日期,见图所示。
图 利用工作表事件产生日期
本例文件参见光盘:..\ 第十二章\只在A1单击更新日期.xlsm
在状态栏显示选区的字母、数字、汉字个数
〖案例要求〗:选择单元格中任意区域时,在状态栏显示选区中的字符个数,以及字母个数、数字个数和汉字个数。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(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, ) Is Nothing Then End
For Each rng In Intersect(Selection, )
'汇总所有单元格的字符数
字符数 = 字符数 + 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
其它符号 = 其它符号 + 1
End IF
Next
Next
= "字数:" & 字符数 + 0 & "个,其中:" & "汉字:" & 汉字 + 0 & "个," & _
"字母:" & 字母 + 0 & "个," & "数字:" & 数字 + 0 & "个," & "其它符号:" & 其它符号 + 0 & "个"
End Sub
(3)返回工作表界面,选择空白区域,状态栏无任何反应;如果选择已用区域中的非空单元格,在状态栏会提示字数、汉字个数、字母个数、数字个数和其它符号的个数。其它符号指标点、空格、片假名等等,见图所示。
图 在状态栏显示选区字数
本例文件参见光盘:..\ 第十二章\选区字符统计.xlsm
实时监控单元格每一次的编辑数据与时间
〖案例要求〗:保存B列每个单元格的修改记录,包括修改的数据和时间。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
'声明工作表事件,当编辑单元格触发事件
Private Sub Worksheet_Change(ByVal Target As Range)
IF <> 2 Or = Or = Then Exit Sub
= False
Dim 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(, 1)
'如果已经有批注
IF Not Is Nothing Then
'在原来的批注后添加时间及内容(适合数据类的记录,如果单元格中是长长的文本,没有监控的必要)
& Chr(10) & tim & ": " & IIF(rng = "", "【清空】", rng)
Else
'否则直接添加批注
tim & ": " & IIF(rng = "", "【清空】", rng)
End IF
'显示批注
= True
'自动调整大小
= True
= False
Next
= True
End Sub
以上事件过程在编辑单元格或者利用快捷键【Ctrl+Enter】批量编辑单元格时触发。过程首先查检单元格是否存在批注,如果没有则添加当前日期与单元格内容;如果有则在原字符串之下一行记录当前时间和内容。最后将批注的外框设定为自动适应批注内容。
(3)返回工作表界面,在A列输入收支项目,在B列输入金额,那么B列的任意单元格的编辑记录都会在批注中体现出来,见图所示:
图 记录当前输入的数据及时间
(4)删除B3的值,然后再查看B3的批注,它保留了第一次输入的值-1250,让用户可以知道所有的编辑记录及每次的编辑时间。如果是删除数据则记录的是“【清空】”,见图所示。程序员也可以根据需求修改删除数据时的显示字符串;
图 清空单元格时记录时间及保留以前的记录
(5)选择B4:C6区域,输入数字400后按下快捷键【Ctrl+Enter】表示在多单元格同时输入数据,然后查看批注,可以发现仅仅B列的单元格会按要求记录时间的内容,其它列忽略。见图所示:
图 多单元格输入时倮留B列的编辑记录
本例文件参见光盘:..\ 第十二章\在批注中存放所有修改记录.xlsm
利用数字简化公司名输入
〖案例要求〗:某公司有五个客户,分别为“广东长风汽车有限制公司”、“湖南衡大塑胶生产厂”、“江苏天信集团”、“珠海电信公司”和“北京天虹印刷厂”,在工作表A列中希望可以通过数字1、2、3、4、5来完成五个常用公司名称的录入工作。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
'声明工作表事件,当编辑单元格时触发事件
Private Sub Worksheet_Change(ByVal Target As Range)
'仅仅对第一列生效
IF = 1 Then
'仅在单个单元格编辑时进行替换
IF > 1 Then Exit Sub
Select Case Target
Case 1
Target = "广东长风汽车有限制公司"
Case 2
Target = "湖南衡大塑胶生产厂"
Case 3
Target = "江苏天信集团"
Case 4
Target = "珠海电信公司"
Case 5
Target = "北京天虹印刷厂"
End Select
End IF
End Sub
以上事件过程限制A列输入数字1、2、3、4、5才进行替换,其它区域忽略。
(3)返回工作表界面,在A2单元格输入1,见图所示。当单击回车键后,单元格中的1将转换成“广东长风汽车有限制公司”,而且是永久性地转换,不能还原为1,见图所示:
图 在A列单个单元格输入1
图 将1替换成广东长风汽车有限制公司
本例文件参见光盘:..\ 第十二章\利用数字简化公司名输入.xlsm
录入数据时自动跳过带公式的单元格
〖案例要求〗:图是一个产量统计表,其中D列、F列和G列分别利用公式计算率品数、不良率和良品率。现要求录入数据时,总是自动定位于下一个待输入数据的单元格,不需要手工移动方向键。即C2输入数据后,自动选择E2;E2输入数据后自动选择H2;H2输入数据后自动选择A3,从而实现快速录入数据。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'仅对单选时生效
IF = 1 Then
'设定单击回车键时向右移动
= xlToRight
'当选择I列时返回A列
IF = 9 Then (1, -8).Select
'如果有公式则选择右边一列
IF Then (0, 1).Select
End IF
End Sub
以上事件在选择单元格时触发,当录入数据并单击回车键时,光标自动向右移动。如果右边一个单元格有公式,则表示不用输入数据,自动定位于它的右边一个单元格,如果该单元格仍然有公式则再选择右边的单元格,直至选择不包含公式的单元格为止;如果当前选择单元格是I列,那么自动跳转到A列下一行第一个单元格,从而实现自动定位。
(3)返回工作表界面,在B3输入产量,活动单元格成为C3。当C3输入废品数量后,活动单元格成为E3,因为B3有公式。当E3输入数据后活动单元格成为H3。当G3输入操作员姓名后,活动单元格将成为A4.
图 D列、F列和G列带有公式的产量表
虽然保护单元格也可以实现本例中相同的功能,但相对于VBA事件的灵活性则差一些,而且保护方式会带来一些后遗症,例如未保护区域无能合并、工作表中不能插入行等等。
本例文件参见光盘:..\ 第十二章\自动跳过公式区.xlsm
在工作表的标题行禁用左、右键
〖案例要求〗:工作表中有两个行标题和一个列标题,现要求在该标题行和标题列中禁止使用左右键,从而避免误删除标题。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Dim rng As Range
'声明事件过程,在选择单元格时触发事件
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'如果选区行号大于2而且列号大于1
IF > 2 And > 1 Then
Set rng = Target '将选区赋与变量Rng
Else
'选择上一次选择的区域Rng
IF rng Is Nothing Then Exit Sub Else
End IF
End Sub
'声明事件过程,在右键单击单元格时触发
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
'如果选区行号大于2而且列号大于1
IF > 2 And > 1 Then
Cancel = False '允许使用右键
Else
Cancel = True '禁止使用右键
End IF
End Sub
第一个事件过程中,如果选择了非标题区域,则记录当前选区地址,如果选择了标题区,则返回上一次所选择的单元格区域,从而实现禁用左右。
第二个事件过程直接对参数Cancel赋值为True来禁用右键。
(3)返回工作表中,选择B10然后再选择A10,那么程序会自动将活动单元格还原为B10,因为A列禁用左键。如果测试第一行和第二行也有同样效果。
本例文件参见光盘:..\ 第十二章\行列标题禁用左右键.xlsm
对选择区域进行背景着色
〖案例要求〗:Excel 2007的选区非常不明显,在输入和查看数据时不方便。本例实现选择单元格时突出显示指定的区域。如果选择单元格,则同时突出单元所在的行与列;如果选择区域,则突出区域本身。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
'声明事件过程,当选择单元格时触发
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error Resume Next
'删除可能存在的条件格式(第一次单击时没有“行”“列”“区域”三个名称)
[行].
[列].
[区域].
'定义三个名称
= "行"
= "列"
= "区域"
'如果选择一个单元格对行与列添加条件格式背景
IF = 1 Then
'对当前行添加条件格式
With [行].
.Delete
.Add xlExpression, , "TRUE"
'指定当前行条件格式中的背景色
.Item(1). = 14
End With
'对当前列添加条件格式
With [列].
.Delete
.Add xlExpression, , "TRUE"
'指定当前列条件格式中的背景色
.Item(1). = 14
End With
Else
'当选择多单元格时则对选区添加条件格式背景色
With [区域].FormatConditions
.Delete
.Add xlExpression, , "TRUE"
.Item(1). = 14
End With
End IF
End Sub
以上过程首先删除三个名称“行”、“列”与“区域”所代码表区域的条件格式,然后重新定义名称,并根据当前选区的单元格个数添加不同的条件格式。条件格式中可以设置单元格背景色,也可以是单元格边框,甚至字体颜色,读者可以根据需求修改。
(3)返回工作表界面,单击D3,那么D列和第3行都会通过背景颜色来突出显示,见图所示:
图 突出当前行与当前列
(4)选择B2:E5,那么B2:E5区域将会突出显示,见图所示:
图 突出当前选区
本例文件参见光盘:..\ 第十二章\背景着色.xlsm
适用于指定区域的自动更正
〖案例要求〗:Excel的自动更正默认对当前工作表簿所有单元格生效,现需在指定的区域中实现符号“>”自动更正成“→”。而且区域由用户随意指定。
〖实现步骤〗:
(1)在工作表标签栏对需要设置工作表事件的工作表单击右键,选择菜单【查看代码】;
(2)在代码窗口中录入以下工作表事件过程代码:
Sub 选择区域()
Dim address As String
On Error Resume Next
'如果选择单元格则获取选区地址,否则获取整个工作表中单元格地址
IF TypeName(Selection) = "Range" Then
address = (0, 0)
Else
address = (0, 0)
End IF
'让用户选择允许自动更正的区域,默认地址为变量Address所代表的区域
'将后将该区域存入辅助单元格(在2003中是IV1,2007中是XFD1)
Cells(1, ) = ("请选择需要自动更正的区域" & Chr(10) _
& "如果想取消自动更正则按“取消”", "选择区域", address, , , , , 8).address
'如果有错误(单击了“取消”)则将变量Rng赋值为nothing
IF Err <> 0 Then Cells(1, ) = ""
End Sub
以上过程用于指定允许自动更正的区域,且将地址存在第一行最后一个单元格,可以随时手动修改该地址。
' '声明过事件过程,编辑单元格时触发事件
Private Sub Worksheet_Change(ByVal Target As Range)
'防错,当用户在辅助区输入不规范的地址时可以不中断程序
On Error Resume Next
'如果辅助单元格是空白则结束过程
IF Cells(1, ) = "" Then End
'如果当前选区不在指定的区域也结束过程
IF Intersect(Target, Range(Cells(1, ))) Is Nothing Then End
'对编辑区域与指定的Rng区域重叠区域进行更正
Intersect(Target, Range(Cells(1, ))).Replace ">", "→", xlPart
End Sub
以上事件在编辑单元格时触发。如果在指定的区域以外编辑单元格或者指定区域为Nothing时,那么自动结束过程;否则将区域中的符号“>”替换成“→”。
(3)返回工作表界面,使用快捷键【Alt+F8】打开“宏”对话框,选择并执行程序“选择区域”。因代码输入在工作表事件代码窗口中,Excel将会把工作表名与宏名一起罗列在“宏”对话框中。执行过程时会弹出图所示对话框,让用户指定允许自动更正的区域。假设选择B列,那么在单元格输入符号“>”时会自动转换成“→”。而且因代码中使Replace的第三参数使用了xlPart,表示近似匹配,那么单元格中输入“方向>”,那么程序同样也会将字符串中的“>”符号转换成“→”。而在其它任何区域输入“>”将会忽略;
(4)如果在跨列区域输入“>”,那么仅仅对B列进行替换成。例如B1:C3中同时输入“>”,程序仅仅替换B列中的“>”;
图 选择允许自动更正的区域 图 跨列输入时替换B列的“>”
(5)删除第一行最后单元格中人地址,或者重新执行过程“选择区域”,并单击取消按钮,那么B列的自动更正功能立即取消。
本例文件参见光盘:..\ 第十二章\在指定区域实现自动更正.xlsm
ActiveX控件事件案例
ActiveX控件包括近百个控件,用户还可以自定义后缀名为“.ocx”的控件。ActiveX控件可以强化表格与VBA代码之间交互功能,而且它比窗体控件更强大的地方在于它支持事件。
鼠标移过时切换按钮颜色
〖案例要求〗:网页中可可鼠标移过时切换按钮颜色或者文字颜色,本例实现对工作表中的命令按钮完成相同功能:鼠标指向的按钮蓝色背景显示,其它按钮则灰色背景显示。
〖实现步骤〗:
(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【命令按钮(ActiveX控件)】,并在工作表中按下左键拖动,绘出一个按钮;
(2)按下Ctrl键的同时利用左键拖动按钮,从而复制一个相同的按钮;
(3)再次复制一个按钮;
(4)在任意按钮上单击右键,选择菜单【查看代码】进入工作表事件代码窗口;
(5)删除默认产生的代码,并输入以下代码:
'第一个按钮的鼠标移过事件
Private Sub CommandButton1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'第一个按钮的背景色为蓝色,其余两个为灰色
= &HFF8080
= &HC0C0C0
= &HC0C0C0
End Sub
'第二个按钮的鼠标移过事件
Private Sub CommandButton2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'第二个按钮的背景色为蓝色,其余两个为灰色
= &HFF8080
= &HC0C0C0
= &HC0C0C0
End Sub
'第三个按钮的鼠标移过事件
Private Sub CommandButton3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'第三个按钮的背景色为蓝色,其余两个为灰色
= &HFF8080
= &HC0C0C0
= &HC0C0C0
End Sub
以上三个过程分别为三个按钮的鼠标移过事件,当鼠标移过某按钮时,将当前按钮的背景色设置蓝色,其它两个按钮的颜色设置为灰色。
(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式,使工作表中的控件处于可用状态;
(7)将鼠标指针指向第二个按钮,那么第二个按钮将呈然蓝色显示,其它两个按钮呈灰色显示,见图所示。如果鼠标移向第三个按钮,则第三个按钮蓝色显示,其它两个按钮灰色显示。
图 鼠标移过时突出当前按钮
本例文件参见光盘:..\ 第十二章\切换按钮背景色.xlsm
鼠标移动录入姓名
〖案例要求〗:借用组合框的下拉菜单在单元格录入姓名,但是不需要鼠标单击目标,而是鼠标移动时将姓名录入到指定的单元格。
〖实现步骤〗:
(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
'让组合框自动调整大小
= True
'将组合框中鼠标箭头下的姓名赋与A1
[A1] = [F1:F10].Cells(Y \ + + 1)
End Sub
(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而激活组合框;
(7)单击组合框,鼠标移过下拉列表中任意项目,该项目将出现在A1单元格中,见图所示:
图 鼠标移过组合框输入姓名到A1
本例文件参见光盘:..\ 第十二章\鼠标移动录入姓名.xlsm
鼠标移过组合框时加载图片
〖案例要求〗:借用组合框的下拉菜单在单元格显示图片。当鼠标移过组合框时,调用当前工作簿同路下PIC文件夹中的同名图片。图片及图片名如图所示:
图 当前工作簿同目录下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 Next
'让组合框自动调整大小
= True
= True
'将组合框中鼠标箭头下的姓名赋与A1
= LoadPicture( & "\PIC\" & [A3:A22].Cells(Y \ + + 1) & ".jpg")
End Sub
以上代码中“ = True”表示让图象控件随加载图片的大小而变化。所以当文件夹下图片大小不同时,图象控件会相应的变化。
(8)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式;
(9)单击控件,鼠标箭头从组合框的下拉列表中移动时,图像控件中会根据鼠标箭头下的图片名而调用不同的图片。
图 鼠标移过组合框加载图片到图像控件
根据需要,也可以将辅助区的文件名存放到其它区域再隐藏起来,从而不影响当前表其它数据;还可以将它存到其它工作表中,VBA仍然根据列框中的文件名调用当前工作簿路下的PIC文件夹中的同名图片。
LoadPicture用于对图像控件加载图片,它的完整浯法如下:
= LoadPicture( pathname )
其中参数pathname是一个图片文件的完整路径。
本例文件参见光盘:..\ 第十二章\鼠标移过加载图片.xlsm
鼠标移过列表框时输入品名与单价
〖案例要求〗:当鼠标移过列表框时,将鼠标箭头下的产品与单价同时引入B列最后一次选择的单元格。
〖实现步骤〗:
(1)首先确保当前窗口为工作表,然后单击功能【开发工具】\【插入】\【列表框(ActiveX控件)】,并在工作表中按下左键拖动,绘出一个列表框;
(2)在E1:F11建立辅助区,存放产品的品名与单价。也可以将辅助区建立在其它工作表;
(3)右键单击列表框,从菜单中选择【属性】,将其“ListFillRange”属性设置为E2:E11,表示列表框中引用E2:E11的值;
(4)再右键单击列表框,选择菜单【查看代码】,进入工作表事件代码窗口;
(5)删除默认产生的代码,并输入以下代码:
'声明工作表事件过程,在选择单元格时触发事件
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
With ListBox1
'只有选择第二列时出现列表框
IF = 2 Then
.Visible = True '显示列表框
'设定列表框的边距、高度和颜色,列表框显示在右下方一个单元格
.Left = Target(1).Offset(0, 1).Left
.Top = Target(1).Offset(1, 1).Top
.Height = * + 4
.BackColor = &HFFC0C0
Else
'否则隐藏
.Visible = False
End IF
End With
End Sub
'声明工作表事件过程,鼠标移过列表框时触发
Private Sub ListBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'将品名和单价同时输入最后一次选择的单元格
(1) = [E2:E11].Cells(Y \ + + 1) _
& "(单价" & [F2:F11].Cells(Y \ + + 1) & ")"
End Sub
(6)返回工作表界面,单击功能区【开发工具】\【设计模式】,从而退出设计模式;
(7)在B列以外的任意单元格单击,列表框自动隐藏;
(8)选择B列任意单元格,在活动单元格的右边一列、下边一行将出现列表框。当鼠标箭头移过列表框中任意品名时,在单元格中将同时产生品名与该产品的单价,见图所示:
图 鼠标移过时输入品名与单价
RangeSelection属性返回工作表中最后一次选定的单元格。而Selection表示当前选择的对象。例如选择A1单元格再单击图表,那么此时RangeSelection代表A1,而Selection代表图表。
本例文件参见光盘:..\ 第十二章\鼠标移过列表框输入品名与单价.xlsm
登堂篇:VBA数组、窗体与控件
第十三章
数组基础
数组在是VBA中很重的要概念。善用数组可以简化代码,也可以提升VBA程序的执行效率。
学习数组之后,读者可能会发现前面12章的很多代码都可以用数组来重写,从而大大地提升执行效率,事实上正是如此。
本章要点:
数组基础
内置数组函数
数组基础
数组主要用于批量地管理数据,或者为了代码提速度,将区域对象转换成内存数组,从而减少读者时间。
在VBA中,善用数组可以大大地对程序提速,甚至完成某些其它方式无法完成的工作。
数组概念
数组就是一个列表或一组数据表。它是连续可索引的具有相同内在数据类型的元素所组成的集合,数组中的每一元素具有唯一索引号。更改其中一个元素并不会影响其它元素。
例如“{1,2,3}”就是一个数组,它的每个成员拥有相同的数据类型,利用索引号可以获取数组中的每个成员。该索引号总是唯一的。
数组存于内存中,可以利用索引号获取该集合中每一个子集。鉴于这个特性,它引申出另外两个知识点:
读写速度快
VBA读取对象中的值永远慢于读取内存中的值,所以只有一有机会,VBA爱好者总会袋借用安适组来对程序提速。
无法永远保存
数据存入工作表区域,可以永久保存。但存入内存中的变量数组和常量数组却受其作用域影响生命周期。过程级别的私有变量数组变量或者常量数组在过程结束后会自动释放,结束其生命周期;而公有的变量数组常量数组在Excel应用程序关闭后会自动释放。也就是在重启Excel后,以前的任何数组都不再存在。
数组可以依据两个标准来分类。按数组元素是否固定可以分为静态数组和动态数组;按其维数可以分为一维数组和二维数组、三维数数……在本节对所有数组的相关知识都会进行详细叙述及演示。
数据的维数
数组可以是一维、二维……直到六十维。然而工作中通常使用一维数组和二维数组,因为Excel程序的每一行或者每一列就可以转换成一维数组,而多行多列的区域可以转换成二维数组,日常工作正是以行、列或者区域中为裁体来处理数据,所以本书中也以一维和二维数组为中心演示数组的功能与实际应用。
1.一维数组
数组的值可以取自于单元格区域,将区域直接赋值到数组。那么借用区域来理解数组也就成了最直观的一种方式。
例如图中A1:F1的是一行横向区域,用工作表中的数组公式可表示为“={1,2,3,4,5,6}”;而图中B1:B6是一列纵向向区域,用工作表中的数组公式可表示为“={1;2;3;4;5;6}”。
图 一维横向数组公式 图 一维纵向数组公式
VBA中的数组也可以像上图中的工作表数组公式一样表示横向和纵向的一维数组。例如:
[{1,2,3,5,6}]——表示一维横向数组
验证它是否一维横向数组的最好办法是将它赋值给一相同大小的单元格区域。代码如下:
Sub 赋值()
[a1:f1] = [{1,2,3,4,5,6}]
End Sub
当执行以上过程后,可以得到图的结果,那么[{1,2,3,4,5,6}]是一维横向数组。
[{1;2;3;4;5;6}]——表示一维纵向数组
同样可以借用区域赋值的方式验证:
Sub 赋值()
[b1:b6] = [{1;2;3;4;5;6}]
End Sub
以上过程可以获得与图所示同样的效果。
同时,根据以上两个赋值的过程,我们可以看到数组在VBA中的优势,即不需要对单元格循环赋值,可以将原本需要循环6次的操作集中在一次完成赋值。
2.二维数组
图中B1:E4是四行四列的区域,利用工作表数组公式对这个区域赋值可以使用以下公式:
={1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}
图 二维数组
在VBA中也可以利用数组表示是一个二维数组,例如:
[{1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}]——四行四列的二维数组
执行以下过程可以完成图所示效果:
Sub 赋值()
[b1:e4] = [{1,1,1,1;2,2,2,2;3,3,3,3;4,4,4,4}]
End Sub
利用索引号获取数组中的元素
类似于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——表示数组中第一个元素的索引号为1
Option Base语句只能置于模块的顶部,而且可选值只有0和1。因为默认状态即为0,那么需要“Option Base 0”时可以忽略。
Option Base语句仅仅作用于当前模块,如果需要修改第一元素默认索引号为1时,需要所有模块都使用该语句。
声明数组与赋值
数组与其它变量一样,需要声明后再使用,犹其是数组更需要显示声明。
1.声明数组变量
声明数组和其它变量一样,可以使用Dim、Static、Private或Public语句来声明。标量变量(非数组)与数组变量的不同在于通常必须指定数组的大小。若指明数组的大小,则它是固定大小数组。若程序运行时数组的大小可以被改变,则它是个动态数组。
在声明数组变量时,可以指定数组的第一元素索引号,也可以指定数组的维数。
当数组变量的参数是一个数值时,表示它是一维横向数组,元素个数等于该值加1。例如:
Dim arr(5)——表示声明一个具有6个元素的横向一维数组,其数据类为变体Variant
Dim 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列的二维数组,默值第一元素索引值为0
Dim arr(1 To 3,1 To 2) As String——表示声明一个3行乘2列的二维数组,默认第一元素索引值为1
VBA允许一个二维数组中不同维的第一元素索引值不同。例如:
Dim arr(1 To 4, 2) As String——表示4行乘3列的二维数组,第一维中第一元素的索引值是1,而第二维中第一元素的索引值是0
2.对数组变量赋值
相对于标量变量赋值,对数组变量赋值的方式更复杂,因为数组是多个元素的集合。
数组赋值通常采用三种方式:利用循环逐个赋值、利用Array对一维数组变量赋值、直接将区域赋与数组。
以下过程是对数组逐个赋值,最后输入数组中第三个元素:
Sub 数组赋值()
Dim arr(3) As String, Item
'循环数组的四个元素
For Item = 0 To 3
'逐个赋值,将A1:A4的值赋与每个变量
arr(Item) = Cells(Item + 1, 1)
Next
MsgBox 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 = (Array("甲", "乙", "丙", "丁"))
[a1:a4] = arr '验证:将数组的值导出到A1:A4
End Sub
直接将区域的值赋与变量也是工作中较常见的赋值方式。例如在区域中循环、查找某个字符串,速度远远低于在内存中查找某字符串,此时通常将区域赋与数组,然后在数组中进行查找。
下例中即为将区域的值赋与数组
Sub 数组赋值()
Dim rng
rng =
End Sub
如果需要从数组变量中取值,可以使用“MsgBox rng(2, 3)”,但不能使用“ MsgBox rng(2)”这种形式取值。
提示:数组的维数一旦在声明时指定,就不能再改变。如果声明是不确定维数组,则需要声明为动态数组。
静态数组与动态数组
静态数组在执行期间不可改变其上界(最后一个元素的索引号),而动态数组可以随时修改其上界。
静态变量的声明方式前面已经讲过,声明变量时指定其大小即可。例如:
Dim arr(3) As Long
Dim arr2(4, 1, 1 To 5) As Byte
而声明动态的数组时,需要Dim语句配合ReDim语句或者ReDim Preserve语句来实现。ReDim语句或者ReDim Preserve语句的作用是为动态数组变量重新分配存储空间,包括指定维数及声明其上界。但ReDim语句重置数组大小时,会使数组中的值丢失;而ReDim Preserve语句重置数组的大小时可以保留原数组中的值。
可以使用 ReDim 语句反复地改变数组的元素以及维数的数目,但是不能在将一个数组定义为某种数据类型之后,再使用 ReDim 将该数组改为其它数据类型,除非是 Variant 所包含的数组。通过以下代码可以比较它们两者的区别:
假设工作表中有图所示数据,将区域的值赋与数组后再分别用两种方法重置数组大小,看看它们的变化:
Sub 重置数组()
Dim arr(), arr2() '声明一个数组
arr = [A1:E10].Value
arr2 = [A1:E10].Value
ReDim 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
当执行以上过程后,其对果见图所示,ReDim语句重置数组大小后,数组Arr的值全部丢失,而ReDim Preserve语句重置后的数组Arr2中的值却保留下来:
图 工作表数据 图 重置数组大小后
内置数组函数
Excel中有很多内置的用于数组的函数,利用它们可以对数组灵活地进行操作。当然,如果感觉不足时,也可以自己定义数组函数。
Array:创建一个数组
Array函数用于创建一个包含数组的 Variant。它只能创建一维横向数组。例如:
Array("甲", "乙", "丙", "丁")——表示包括四个元素的一维横向数组。可以利用索引号从数组中逐个取出所有值,例如:
Array("甲", "乙", "丙", "丁")(1)——表示“乙”,从数组中获取第二个元素的值。Array方式创建的数组默认状态下下界为0,随Option Base语句的设置而变化。
如果需要对A1单元格写入“姓名”,对B1写入“成绩”,对C1写入“名次”,那么借用Array可以一次完成,而不需要赋值三次,代码如下:
[a1:c1] = Array("姓名", "成绩", "名次")
Array的参数个数可以就是数组的上界,数组上界的大小受计算机的可用内存限制,内存越大,它支持的上界就越大。所以理论上只要内存够大,数组的上界可以无穷大。
Array的参数中各元素的值可以不互相干扰,即它可以是任意数组的数据。例如:
Array(123, "ABC", Date, Range("A2"), Format(Now, "e-hh-dd"),12+5)
以上数组中包括是文本、数值、日期,单元格对象以及计算表达式,Array可以有效地将它们转换成一个数组。并可以将该数组的值一次性赋与区域中。
提示:Array只能对Variant型变量赋值,且声明该变量时不能包含括号。
Isarray:判断是否是数组
Isarray函数可以返回Boolean值,指出其参数是否为一个数组。例如:
IsArray(Array("姓名", "成绩", "名次"))——结果为True
IsArray([a1:a2].Value) ——结果为True
IsArray(Range("A1").Value)——结果为Flase
IsArray(Array(1))——结果为True,虽然只有一个元素,仍然是数组
Index:从数组中取值
Index是一个工作表函数,非VBA函数,但是可以在VBA中调用。它的语法如下:
Index(arrsy,row_num,column_num)
第一参数表示数组;第二参数是表示取数组中行号,下界为1;第三参数表示数组中列号,下界为1。Index从数组中取值时完全不受Option Base语句的影响。
从以下过程可以比较Index与数组索引号取值的区别:
Sub 从数组中取值()
MsgBox (Array(123, "ABC", Date, "你我他"), 1)
MsgBox Array(123, "ABC", Date, "你我他")(1)
End Sub
Index获取的值为123,而利用索引号获取的值为“ABC”。
也可以从二维数组中获取值,Index需要指定第三参数:
Sub 从数组中取值()
aa = [a1:C10].Value
MsgBox (aa, 3, 3)
End Sub
Transpose:转置数组
Transpose也是工作表函数,但在数组中常使用它的转置数组,将数组在横向与纵向之间转换。
VBA中声明的一维数组总是横向的,当需要产生一维纵向数组时通常利用Transpose函数实现转换。例如在A1:A7区域产生星期一到星期日的字符串,那么可以使用以下过程:
Sub 对纵向区域赋值()
Dim arr As Variant
arr = Array("星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日")
[a1:a7] = (arr)
End Sub
Transpose是工作表函数,需要前置WorksheetFunction对象。
除了本例中一次性赋值的数组外,对其它数组也常常需要用到Transpose进行转置。现举一数组查询的实例演示数组的优势和两维数组的定义与转置。
要求:工作表中有A1:A2001中有2000个学生的成绩,现需将不及格的姓名罗列在D列,将成绩罗列在E列,采用两种方式完成。
(1)在VBE界面中单击菜单【插入】\【模块】;
(2)在模块中输入以下两段代码:
Sub 查询不及级人员1() '非数组
Dim arr() As Variant, Item As Integer, rng As Range
Dim Tim As Long
Tim = Timer
'遍历所有成绩
For Each rng In Range([B2], Cells(, "B").End(xlUp))
'如果成绩小于60
If rng < 60 Then
Item = Item + 1 '累加变量
'将查找到到了成绩和姓名罗列在D、E列
Cells(Item, "D") = (0, -1).Text
Cells(Item, "E") =
End If
Next rng
'记录时间
MsgBox Format(Timer - Tim, "秒")
End Sub
以上过程采用循环、逐个赋值的方式将区域中查询到的目标导出到单元格,在笔者的计算机中查询时间超过秒钟。
Sub 查询不及级人员2() '数组方式
Dim arr1 As Variant, arr2() As Variant, Item As Integer, MaxRow As Integer, Counter As Integer
Dim Tim As Long
Tim = Timer
'记录最大行数
MaxRow = Cells(, "B").End(xlUp).Row
'将成绩赋与数组,后续的操作都基于数组
arr1 = Range("A2:B" & MaxRow)
'遍历数组中所有成绩
For Item = 1 To MaxRow - 1
'如果成绩小于60
If 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 If
Next
'将第二个数组转置后导出到D列
Cells(1, "D").Resize(Counter, 2) = (arr2)
MsgBox Format(Timer - Tim, "秒")
End Sub
以上过程首先将区域转化成数组,然后在数组中进行查询,且将查到的目标数组赋与新的数组。当找到所有目录后一次性将数组的值导出到区域中。该过程在笔者的计算机上执行时间在秒钟左右。
图 查找所有不及格人员姓名与成绩罗列在D、E列
在第二个过程中,使用数组后已经大大地提升查询速度。但是在循环体中使用ReDim Preserve语句,相当于声明2000次数组,它仍然需要消耗时间。本例也可以改为数组的声明方式,避过多次重置数组大小这个问题。代码如下:
Sub 查询不及格人员3() '数组方式
Dim arr1 As Variant, arr2() As Variant, Item As Integer, MaxRow As Integer, Counter As Integer
Dim Tim As Long, j As Integer
Tim = Timer
'记录最大行数
MaxRow = Cells(, "B").End(xlUp).Row
'记录不及格的人员个数
j = (Range("A2:B" & MaxRow), "<60")
'将成绩赋与数组,后续的操作都基于数组
arr1 = Range("A2:B" & MaxRow)
'重置第二个数组大小,两列多行
ReDim arr2(1 To j, 1 To 2)
For Item = 1 To MaxRow - 1
'如果成绩小于60
If arr1(Item, 2) < 60 Then
Counter = Counter + 1
'对第二个数组第一列、最末行赋值
arr2(Counter, 1) = arr1(Item, 1)
'对第二个数组第二列、最末行赋值
arr2(Counter, 2) = arr1(Item, 2)
End If
Next
'数组是多行两列的纵向数组,所在不需要转置
Cells(1, "D").Resize(Counter, 2) = arr2
MsgBox Format(Timer - Tim, "秒")
End Sub
以上过程首先利用工作表函数CountIf计算区域中不及格人数,然后一次性对数组分配空间,使其行数等于不及格人数,从而免循环2000次重新分配数组空间。同时也解决了转置的问题。
本例文件参见光盘:..\ 第十三章\查找不及格人员.xlsm
LBound / Ubound:获取数组的上下界
LBound函数和Ubound函数分别用于计算数组的最小下界(或称下标)和数组的最大上界,在VBA的数组应用中,它们的应用极广。
1. LBound
LBound函数返回一个Long型数据,其值为指定数组维可用的最小下标。语法如下:
LBound(arrayname[, dimension])
第一参数表示数组变量的名称,第二参数是可选参数,用于指定返回哪一维的下界。如果省略当做1处理。例如:
Dim A(1 To 10, 3, 1 To 4,-2 To 5)
LBound(A, 1) ——返回1,表示第一维的下界
LBound(A, 2) ——返回1,表示第二维的下界
LBound(A, 4) ——返回-2,表示第车维的下界
2. Ubound
Ubound函数返回一个Long型数据,其值为指定数组维可用的最大上标。语法如下:
Ubound (arrayname[, dimension])
第一参数表示数组变量的名称,第二参数是可选参数,用于指定返回哪一维的上界。如果省略当做1处理。例如:
Dim A(1 To 10, 3, 1 To 4,-2 To 5)
Ubound (A) ——返回10,表示第一维的上界
Ubound (A, 2) ——返回3,表示第二维的上界
Ubound (A, 5) ——返回-2,表示第车维的下界
Split/ Join:文本与数组转化
Split函数与Join函数可以实现字符串与文本的转换。
1. Split函数
Split函数用于返回一个下标从零开始的一维数组,它包含指定数目的子字符串。语法如下:
Split(expression[, delimiter[, limit[, compare]]])
其中各参数含义如下:
表13-1 Split函数参数详解
部分
描述
expression
必需的。包含子字符串和分隔符的字符串表达式 。如果expression是一个长度为零的字符串(""),Split则返回一个空数组,即没有元素和数据的数组
delimiter
可选的。用于标识子字符串边界的字符串字符。如果忽略,则使用空格字符(" ")作为分隔符。如果delimiter是一个长度为零的字符串,则返回的数组仅包含一个元素,即完整的expression字符串
limit
可选的。要返回的子字符串数,–1表示返回所有的子字符串
compare
可选的。数字值,表示判别子字符串时使用的比较方式
例如将字符串“中国,广东,广州”转换成包括三个元素的数组,可以使以下语句:
Split("中国,广东,广州", ",")——注意其中逗号是全角
如果需要验证这个数组,有三种方式:
[a1:c1] = Split("中国,广东,广州", ",")——将数组赋值到区域
MsgBox IsArray(Split("中国,广东,广州", ","))——利用IsArray判断
MsgBox UBound(Split("中国,广东,广州", ","))——计算数组的最大上标
2. Join
Split函数返回一个字符串,该字符串是通过连接某个数组中的多个子字符串而创建的。语法如下:
Join(sourcearray[, delimiter])
其第一参数包含被连接子字符串的一维数组,第二参数是可选参数,用于指定分隔子字符串的字符。如果忽略该项,则使用空格(" ")来分隔子字符串。如果delimiter是零长度字符串(""),则列表中的所有项目都连接在一起,中间没有分隔符。
例如将一个一维数组转换成字符串可以用方式:
Join(Array("中国", "广东", "广州"), "-")——转换后字符串为“中国-广东-广州”
Join(Array("中国", "广东", "广州"), ",")——转换后字符串为“中国,广东,广州”
但是Split函数处理数组时也有一个限制,当数组是通用区域转换得来的就不能对数组的元素转换成字符串。例如以下合并区域的过程无法执行完成:
Sub 合并区域()
Dim arr As Variant
arr = Cells(1, 1).Resize(1, 3).Value
MsgBox Join(arr, ",")
End Sub
Filter:数组的筛选
Filter函数用于返回一个下标从零开始的数组,该数组包含基于指定筛选条件的一个字符串数组的子集。其语法如下:
Filter(sourcesrray, match[, include[, compare]])
各参数含义如下:
表13-2 Filter函数的参数详解
参数
含义
sourcearray
必需的。要执行搜索的一维字符串数组
match
必需的。要搜索的字符串
include
可选的。Boolean值,表示返回子串包含还是不包含match字符串。如果是True,返回包含match子字符串的数组子集。否则返回不包含match子字符串的数组子集
compare
可选的。数字值,表示所使用的字符串比较类型
例如从一串地址中找出所有属于广东的地名,代码如下:
Sub 找出广东的地名()
Dim arr1
arr1 = Array("广东广州", "四川成都", "广东东莞", "湖南长沙", "广东中山", "湖背武汉")
MsgBox Join(Filter(arr1, "广东", True, 1))
End Sub
以上过程中的数组arr1包括六个地名,Filter函数可以将该数组中包含“广东”的名需筛选出来,然后组成一个数组。而Join函数将数组转换成了字符串,其结果为“广东广州 广东东莞 广东中山”。
Filter函数的第二参数为字符串,如果需要使用数组做参数,即在两个数组间筛选,那么需要使用循环。例如以下过程,计算两个数组间的不同项与相同项:
Sub 两个数组中相同项与不同项()
Dim arr1, Arr2, arr3, i As Integer, str
'Arr2有而Arr1没有的项目
arr1 = Array("张松", "陈英杰", "刘明")
Arr2 = Array("古云华", "张松", "张忠英", "黄华丽", "周大明", "刘明新", "朱华", "陈明真")
'遍历数组Arr1三个姓名
For i = 0 To UBound(arr1)
'对Arr2重新赋值,该值为去掉重复项之后的数组
Arr2 = Filter(Arr2, arr1(i), False, 1)
Next i
'最后结果是从Arr2中去除与Arr1中所有相同的项目
MsgBox "Arr2有Arr1没有项目:" & Join(Arr2)
'arr1和arr2共有的项目
arr1 = Array("张松", "陈英杰", "刘明")
Arr2 = Array("古云华", "张松", "张忠英", "黄华丽", "周大明", "刘明新", "朱华", "陈明真")
For i = 0 To UBound(arr1)
'如果两者筛选后产生的数组上界大于等于0,那么表示存在相同项,则将它与变量str串连
If UBound(Filter(Arr2, arr1(i), True, 1)) >= 0 Then str = str & arr1(i) & ","
Next i
MsgBox "共有项:" & Left$(str, Len(str) - 1)
End Sub
以上过程中,前一段将Filter函数的第二参数设置False表示将Arr2中不包含Arr1的每一个元素组成一个数组,该数组中每个元素就是Arr2有而Arr1没有的项目。第二段则借用一个中间变量来记录两个数组共有的项目,将其与逗号串连成一个字符串。
Filter函数在比较两个值是否相同时,采用的近似匹配,所以“广东广州”才后等于“广东”。
本例文件参见光盘:..\ 第十三章\两个数组中相同项与不同项.xlsm
第十四章
开发数组函数与数组应用
善用数组可以对程序大大地提速。
本章就数组的应用进行案例讲解,包括开发数组方面的函数和数据查询等应用。通过本章地学习,读者完全可以借用数组对本书前12章的程序进行改造,提升其执行速度。
本章要点:
自定义数组函数
数组应用案例
自定义数组函数
在第6章中关于开发自定义函数的讲解时曾有一个数组函数,而本节对数组函数再进行深入地研究。
定义数组函数要点
开发自定义的数组函数和非数组函数相比较,更复杂一些。它值遵循一些规则。
首先,在声明函数时一定要声明为变体型Variant,或者不指定数据类型。
其次,按照用户的通常习惯,工作中使用一维纵向数组更多,而Array函数只能产生一维横向数组,将Array产生的数组赋与函数时需要进行转置。
最后,必须要函数的说明中明确指定这是数组函数,否则用户在使用中可能频频出错。
获取工作表目录
〖案例要求〗:获取所有工作表名
〖过程代码〗:
Rem 获取所有工作表名,组成一个数组
Function Sheets() As Variant
'声明变量,包括一个变体型Arr变量,将用它来获取所有工作表名,再将其赋与函数Sheets
'Sheets和Arr都只能声明为变体型
Dim sht As Worksheet, arr As Variant, Item As Integer
'重置数组的大小和维数。其上界等于工作表数量
'因函数名是Sheets,避免产生冲突,必须添加前缀ActiveWorkbook
ReDim arr(1 To )
'遍历所有工作表
For Each sht In
Item = Item + 1
'将工作表名赋与变量数组
arr(Item) =
Next sht
'将数组赋与函数
Sheets = (arr)
End Function
〖注意事项〗:
1.声明函数时必须使用变体型;
2.因Sheets是VBA内置对象名称,在引用工作表对象集合时需要添加对象库;
3.数组Arr默认是一维横向数组,赋与函数时转置与否都可以。因为工作表中也可以转置。
4.为了让工作表新增、删除时公式的结果自动更新,必须使用“Volatile”。
〖功能测试〗:
返回工作表中选择B2:B4并输入以下公式:
=sheets()
注意是数组公式,必须以【Ctrl+Shift+Enter】三键结束。如果需要获取其中部分工表名,可以借用Index函数来实现。例如:
=INDEX(sheets(),ROW(A1))
图 Sheets函数获取工作表
本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsm
星期序列
〖案例要求〗:获取所有星期序列,包括英文和中文两种样式。
〖过程代码〗:
Rem 产生星期序列,有一个可选参数
Function Week(Optional Opt As Byte = 1)
Select Case Opt
'当参数为1或者忽略时产生中文序列
Case 1
Week = (Array("星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"))
'当参数是2时产生英文序列
Case 2
Week = (Array("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
Case Else
'否则为空白
Week = ""
End Select
End Function
〖注意事项〗:
1.当有多种选项时,尽量通过Optional设置一个默认选项,简化用户的公式输入工作;
产生的数组是一维横向数组,面要利用Transpose进行转置。如果不想用Transpose那么可以换一种赋值方式:Week = [{"星期一"; "星期二";"星期三"; "星期四"; "星期五"; "星期六"; "星期日"}]。
〖功能测试〗:
在工作表中选择A1:A7区域,然后入以下公式:
=Week()——产生7个中文星期序列
=Week(2)——产生7个英文星期序列
本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsm
获取区域的唯一值
〖案例要求〗:将引用区域去除重复值,将唯一值利用数组方式罗列出来。
〖过程代码〗:
Function only(rng As Range)
Dim cell As Range, onlys As New Collection, Item As Long, arr
On Error Resume Next
'如果引用区域不在已用区域中则函数返回空文本
If Intersect(rng, ) Is Nothing Then only = "": Exit Function
'将区域限制在已用区域中,防止用户选择整列或者全选工作表时造成死机
For Each cell In Intersect(rng, )
If cell <> "" Then , CStr()
Next
'重置数组大小,其上界等于为重复数的个数
ReDim arr(1 To )
For Item = 1 To
'将不重复值逐个加入数组arr
arr(Item) = onlys(Item)
Next
'将数组转置后赋与only
only = (arr)
End Function
〖注意事项〗:
1. 获取唯一值有很多方法,Countif或者字典法,或者Collection对象法,经过笔者的多次测试,利用Collection对象获取唯一值速度最快。它的一个特点是对象集合中所有对象不能存在重值,那么将区域中所有元素加入到该对象集合中后再逐个取出来,就可以过滤掉所有重复值。
2.如果要对参数所代表的区域进行循环时,通常都需要限制为该区域与工作表已用区域的交集,从而避免用户以整列、或者以“1:65536”做为参数时造成计算机进行假死状态。而获取已用区域时需要的一个技巧是:用“)”获取目标工作表的已用区域。因为用户可能会引用其它工作表的区域做为参数的函数。
〖功能测试〗:
假设A1:A10有图所示的数据,选择B1:B10可以获取该区域中唯一值:
=only(A1:A10)
因唯一值仅四个,那么在超过4个单元格以上的区域输入公式后,在第五个单元格开始的区域会产生错误值,解决这个问题有两个办法。一是用ROWS函数判断数组only的元素个数,将超出部分赋值为空。公式如下:
=IF(ROW(1:10)>ROWS(only(A1:A10)),"",only(A1:A10))——公式结果见图所示。
第二个办法是按普通公式方式输入公式,以Index函数从数组中逐个取值:
=IFERROR(INDEX(only(A$1:A$10),ROW()),"")
图 获取A1:A10唯一值 图 消除错误值
本例文件参见光盘:..\ 第十四章\自定义数组函数.xlsm
数组应用案例
VBA读写数组的速度远远快于读写单元格或者工作簿对象,所以操作单元格时往往可以转换为对数组的操作,从而对程序提速。本节就查找方面的应用进行6个案例演潮头,加深读者对数组的理解。
将按姓名排列的纵向学员表转置为按班级横向排列
〖案例要求〗:将图中按姓名纵向排列的学员表转换换横向排列,以三个班级为分类标准从D列开始罗列出来。
〖过程代码〗:
Sub 区域转置()
Dim arr1 As Variant, i As Long, MaxRow As Long, a, b, c
Dim arr() As Variant
'记录最后一个非空行
MaxRow = Cells(, 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 Select
Next i
'将数组赋与区域,多行的学员表已转置为多列
Cells(1, 4).Resize(3, MaxRow) = arr
End Sub
〖注意事项〗:
、B列和D1:D3两个区域都有必要转化成数组,然后从数组中读写,从而提升效率;
的作用是让程序自动计算非空单元格最大行,当数据增减时可以体现兼容性;
3. ReDim Preserve语句重置数组时可以保留其值,本例中不能使用ReDim。
〖功能测试〗:
返回工作表,执行过程“区域转置”,可以将AB列的学员表转置并存Cells(1, 4).Resize(3, MaxRow)区域。
图 按姓名排列的学员表
本例文件参见光盘:..\ 第十四章\转置区域.xlsm
多表学员资料查询
〖案例要求〗:在多工作表中查询学员资料,包括成绩、姓名、地址、工作表及学号。
图 三个班级的成绩表
〖过程代码〗:
Sub 成绩搜索()
Dim Tim, arr(), intRows As Long, 姓名 As String, firstAddress As String, cell As Range
'关闭屏幕更新
= False
'清除上次查询信息
Range("A:F").Clear
'设定查找目标
姓名 = ("您想查找谁的成绩?可以输入一个或者多字", "查找目标", , , , , , 2)
Tim = Timer '初始化时间变量
'遍历工作表(当前存放结果的工作表即除外)
For i = 1 To - 1
'在每个表A列中查找
Set cell = Sheets(i).Range("A:A").Find(what:=姓名, LookAt:=xlPart)
If Not cell Is Nothing Then
firstAddress = (0, 0)
Do
intRows = intRows + 1 '累加计数器
'重定义数组大小
ReDim Preserve arr(1 To 6, 1 To intRows)
arr(1, intRows) = Sheets(i).Name '数组第一子项目赋值为查找到的数据所在工作表名
arr(2, intRows) = (0, 0) '数组第二子项目赋值为查找到的数据的单元格地址
arr(3, intRows) = '数组第三子项目赋值为查找到的姓名
arr(4, intRows) = (0, 1).Text '数组第四子项目赋值为查找到的班级
arr(5, intRows) = (0, 2).Text '数组第五子项目赋值为查找到的学号
arr(6, intRows) = (0, 3).Text '数组第六子项目赋值为查找到的成绩
Set cell = Sheets(i).Range("A:A").FindNext(cell)
Loop While Not cell Is Nothing And (0, 0) <> firstAddress
End If
Next
'如果找到有目标
If intRows > 0 Then
'将找到的值赋与单元格区域,然后添加边框与标题
Range("A2:F" & intRows) = (arr)
[A1:F1] = Array("工作表", "地址", "姓名", "班级", "学号", "成绩")
Range("A1:F" & intRows). = xlContinuous
End If
= True
MsgBox Format(Timer - Tim, "") & "秒" '提示总运行时间
End Sub
〖注意事项〗:
1.因学员资料存在多工作表中,所以需要利用循环查找所有存放学员资料的工作表;
2. Find的参数“LookAt:=xlPart”表示模糊查找,输入“张”可以找到所有姓“张”者的资料;
3.查找数据时必须针对未查找数组时的状况进行处理,在本例中借用“intRows > 0”来判断中是否有查找到目标。
〖功能测试〗:
返回工作工作表中后,执行过程“成绩搜索”将弹出一个对话框让用户输入待查询对象的姓名,假设输入“张”,那么查询结果见图所示:
图 多表资料查询
本例文件参见光盘:..\ 第十四章\学员资料查询.xlsm
自定义百家姓序列
〖案例要求〗:生成百家姓序列,使工作表中输入姓氏时可以填充式输入。
〖过程代码〗:
Sub 序列() '生成百家姓序列
= False
On Error Resume Next
Cells(, 1). '清除最后一列
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) '将百家姓加入数组中
Next
For i = 1 To 140 Step 2 '遍历120个复姓
arr(407 + (i + 1) / 2) = Mid(复姓, i, 2) '将复姓追加入数组中
Next i
'将数组导入自定义序列
(arr)
End Sub
〖注意事项〗:
1.百家姓可以去百度等搜索引擎查找,然后将它转换成字符串做代码调用;
2.因每行代码有长度限制,所以对常量赋值时必须将它截断成多行;
3.单姓与复姓的截取时标准不同,所以需要两个变量,MID的第三参数也不同;
4.将百宝姓逐个写入单元格中,然后将区域一次性导入序列也是可行的,然而相对于数组在效率上略差。
〖功能测试〗:
光标位于过程中任意位置,并按下快捷键F5执行过程,然后返回工作表中,在A1输入赵,当A1向下填充时,可以产生百家姓序列,总共477个。见图所示:
图 填充百家姓
本例文件参见光盘:..\ 第十四章\生成百家姓序列.xlsm
查询两列共有项
〖案例要求〗:将AB列中相同的数据提取出来。
〖过程代码〗:
Sub 共有项()
'声明变量,包括三个数组变量,其中从区域转换成数组的变量虽然只有单列,但却不能通过一维纵向数组的方式访问
Dim arr1() As Variant, arr2() As Variant, arr3() As Variant, Item1 As Integer, Item2 As Integer, SameCount, Counter As Byte
'将区域转换成数组
arr1 = Range([A2], Cells(, 1).End(xlUp)).Value
arr2 = Range([B2], Cells(, 2).End(xlUp)).Value
'计算两列中相同数的个数,减少ReDim的次数。如果不计算相数同,那么可以在循环中多次使用ReDim Preserve来解决
SameCount = ("=sumproduct(countif(" & Range([A2], Cells(, 1).End(xlUp)).Address & "," & Range([B2], Cells(, 2).End(xlUp)).Address & "))")
ReDim arr3(1 To SameCount)
'嵌套循环,比较A列的与B列有哪些相同,将相同的值加入数组Arr2
For Item1 = 1 To UBound(arr1)
For Item2 = 1 To UBound(arr2)
'如果相同加入第三个数组,注意第两个数组与第三个数组的参数个数不同
If arr1(Item1, 1) = arr2(Item2, 1) Then Counter = Counter + 1: arr3(Counter) = arr1(Item1, 1)
Next Item2
Next Item1
MsgBox "两列中相同项为:" & Chr(10) & Join(arr3, ",")
End Sub
〖注意事项〗:
1.本例中声明了三个数组,Arr1和Arr2通过一列的区域转换成数组,那么在访问其元素时不能当成一维纵向数组处理,必须使用两个参数,例如Arr1(1,1),而且其默认下界是1不是0。
2. 为了减少数组变量的重置次数,本例中使用了Evaluate方法计算两列是相同值的个数,做为数组的上界。当数组上界很大时,此方法会具有速度上的优势。
〖功能测试〗:
光标位于过程中任意位置,并按下快捷键F5执行过程,两列同相同项为图所示:
图 参赛人员列表 图 相同项列表
本例文件参见光盘:..\ 第十四章\获取相同项目.xlsm
获取文件夹下所有文件详细信息
〖案例要求〗:将指定文件夹中所有文件的文件名、创建时间、上次访问时间、上次修改时间、文件大小、文件类、路径等信息罗列在工作表中。
〖过程代码〗:
Sub 批量获取文件信息() '遍历指定路径两级文件夹内文件信息,如果需要更多级可根据需要修改
Dim fd As FileDialog, Fso, Fold, File, Folds, Filess, arr(), Item As Long
'让用户选择路径
Set fd = (msoFileDialogFolderPicker)
'如果选择了文件夹则记录路径
If = -1 Then
PathStr = (1)
Else
Exit Sub
End If
'设置标题
[a1].Resize(1, 7) = Array("文件名", "创建时间", "上次访问时间", "上次修改时间", "文件大小(b)", "文件类型", "路径")
'利用FSO技术获取文件与文件夹相关信息
Set Fso = CreateObject("")
Set Fold = (PathStr)
Set Filess =
Set Folds =
'将每个文件的名称、创建时间、上次访问时间、修改时间、大小、类型、路径等信息加入数组
For Each File In Filess
Item = Item + 1
ReDim Preserve arr(1 To 7, 1 To Item)
arr(1, Item) =
arr(2, Item) =
arr(3, Item) =
arr(4, Item) =
arr(5, Item) =
arr(6, Item) =
arr(7, Item) =
Next
'将数组导出到单元格并对单元格自动调剌列宽
[a2].Resize(Item, 7) = (arr)
Range("A:H").
End Sub
〖注意事项〗:
1.获取文件的信息有多种方式,本例中采用了FSO技术,在本书第19章将对FSO进行详述;
2. ReDim Preserve语句只能改变数组的末维大小,所以重置大小时必须是横向延伸的数组,而在最后将它转置后再导出到工作表中。
〖功能测试〗:
光标位于过程中任意位置,并按下快捷键F5执行过程,在弹出的对话框中选择文件夹,那么该文件夹中的每一个文件的相关信息都会在工作表中罗列出来。
本例文件参见光盘:..\ 第十四章\批量获取文件信息.xlsm
获取当前表所有批注
〖案例要求〗:图是成绩表,成绩为空或者不及格者有批注。现要求将所有批注及姓名单独提取出现放在新工作表中,便于查看及打印。
〖过程代码〗:
Sub 获取批注()
Dim Nam As Comment, arr(), Item As Long
'遍历所有批注
For Each Nam In
Item = Item + 1
'重新分配数组大小
ReDim Preserve arr(1 To 2, 1 To Item)
'将姓名与批注导入到数组中
arr(1, Item) = "姓名:" & (0, -1)
arr(2, Item) = "批注:" &
Next
'将数组转置后赋与单元格
With
.Name = "获取批注"
.Cells(1, 1).Resize(Item, 2) = (arr)
End With
End Sub
〖注意事项〗:
1.可以利用遍历B列每个单元格的方式,将有批注的单元格对应的姓与与批注提取出来,但循环批注较循环单元格更快;
2.本例也可以先计算批注的数量,然后只需要使用ReDim Preserve一次,直接设定其上界,然后循环体中可以不再使用ReDim Preserve语句。读者可以延着这个思路去练习一次。
〖功能测试〗:
光标位于过程中任意位置,并按下快捷键F5执行过程,提取出来的数据如图所示:
图 成绩表 图 将所有批注提取出来置于新表中
第十五章
认识窗体与控件
窗体与控件可以实现与表格的交互,也可以设计精美的界面。善用控件可以使自己的程序更具个性,以及增强Excel的功能。
本章要点:
UserForm简介
窗体控件一览
设置控件属性
窗体与控件的事件
UserForm简介
UserForm即用户窗体,通过它可以操作工作簿、工作表、单元格、批注、图形对象等等,也可以仅仅利用窗体设计单独的程序,完全脱离单元格、工作表等数据载体。
窗体与控件的用途
VBA中的窗体与控件主要用于以下几方面的:
设计登录窗口
制作数据输入界面
数据查询界面
选项设置窗口
制做程序帮助
本书将对以上各类用作做案例演示。
插入窗体与控件的方法
插入窗体的方法是:在VBE界面中单击菜单【插入】\【用户窗体】,那么当前工程中将出面一个“UserForm1”。
但是插入用户窗体需要注意三点:
首先,需要确保当前有打开的工作簿。如果不存在活动工作簿那么无法插入用户窗体
其次,是当前工程如果受保护,那么必须解除保护才可以插入新的窗体;
当打开多个工作簿时,插入的窗体附属于当前选中的工程,它与活动工作簿无关。
而插入控件则是在已插入窗体的基础上进行,只有已存在窗体的情况下才可以插入控件。
假设当前已存在窗体“UserForm1”,那么双击工程资源管理器中的窗体名“UserForm1”,进入窗体编辑状态,然后单击菜单【视图】\【窗体】可以显示工具箱,在工具箱中有默认的15种控件,用户可以通过单击并在窗体中拖动的方式来插入控件。图是窗体与工具箱:
图 窗体与包含默认控件的工具箱
假设需要在窗体中插入一个文字框控件,那么可以单击工具箱中的“文字框控件”(图标为),然后在窗体中拖动产生一个文字框控件。然后可以用左键按住边框拖动的方式改变其大小。
使用Excel 对话框
除了VBE中插入窗体外,在表中也可以使用窗体与控件。具体步骤为:
(1)在工作表标签上单击右键,从菜单中选择【插入】;
(2)在弹出的对话框中选择“MS Excel 对话框”,从而插入一个宏表对话框;
(3)在表中默认已经产生一个窗体和两个按钮,可以通过开发工具中的表单控件来丰富窗体的内容。例如图中左边是设置时的界面,右边是运行对话框时的界面。
图 Excel 对话框
对话话框的执行方式有两种:在对话框中的空白区单击右键,从中选择【执行对话框】,另一种方法是利用VBA的Show方法显示窗体,例如对话框的名字是“窗体”,那么代码如下:
Sub 执行对话框()
DialogSheets("窗体").Show
End Sub
本例文件参见光盘:..\ 第十五章\执行对话框.xlsm
窗体控件一览
控件位于工具箱中,可以将它加添加到窗体中强化窗体的功能。默认的控件有九个,用户可以对工具箱添加自定义控件。
标签
标签的英文名称是Label,用代码引用该控件时也使用Lable。它在工具箱中的图标为。
在窗体中添加标签时,默认名称和默认Caption都是“Lable1”,添加第二个时默认名和默认Caption都是“Lable2”,可以在代码中以该名称来引用控件。
标签用于在窗体中添加说明性的文本,且该文本在窗体执行阶段是不可修改的,通常由开发者指定标签内容,在物殊情况下也可以根据运行的条件自动选择显示的文本内容。
文字框
文字框的英文名称是TextBox,它在工具箱中的图标为。
在窗体中添加文字框时,默认名称和默认Caption都是“TextBox1”,添加第二个时默认名和默认Caption都是“TextBox2”,用户可以随时修改其名称和Caption属性。
文字框的用途是运行窗体时让用户输入文字或者数值。
命令按钮
命令按钮的英文名称是CommandButton,它在工具箱中的图标为。
命令按钮的用途是用户单击时可以执行一个或者多个任务。它的显示字符Caption在窗体执行阶断不可以修改。
复合框
复合框(也称组合框)的英文名称是ComboBox,它在工具箱中的图标为。
复合框可以画出列表框与文本框的组合。用户可以从列表中选出一个项目或是在一个文框中输入值。
列表框
列表框的英文名称是ListBox,它在工具箱中的图标为。
列表框用来显示用户可以选择的项目列表。如果不能一次显示全部项目的话,可以滚动其滚动条来显示其它项目,也可以通过代码让列框改变高度来适应列表项目的数量。
复选框
复选框的英文名称是CheckBox,它在工具箱中的图标为。
复选框用于创建一个方框,让用户容易地选择以指示出某些事物是真或假。
单选框
单选框的英文名称是OptionButton,它在工具箱中的图标为。
单选框用于显示多重选择,但用户只能从中选择一个项目。
分组框
分组框(也称框架)的英文名称是Frame,它在工具箱中的图标为。
分组框用于创建一个图形或控件的功能组,将窗体中的其它控件分组,特别是有单选框时,分组框有助于用于创建多个单选项。
切换按钮
切换按钮的英文名称是ToggleButton,它在工具箱中的图标为。
切换按钮用于创建一个切换开关的按钮,可以在按下和突起时分别执行不同过程。
多页控件
多页控件的英文名称是MultiPage,它在工具箱中的图标为。
多页控件类似于分组框,可以将内有某种联系的控件单独做为一组显示。区别是多个分组框可以同时显示,而多页控件一次只能显示一页。它的功能与TabStrip控件相近。
滚动条
滚动条的英文名称是ScrollBar,它在工具箱中的图标为。
滚动条提供在长列表项目或大量信息中快速浏览的图形工具,以比例方式指示出当前位置,或是做为一个输入设备,成为速度或者数量的指示器。通常用它替代数字输入。它的功能与旋转按钮相近。旋转按钮的图标为。
图像
图像的英文名称是Image,它在工具箱中的图标为。
图像控件用于在窗体上显示位图、图标,不能显示动画。通常用它做装饰,可以设置背景。
RefEdit
RefEdit也称单元格选择器,它在工具箱中的图标为。
RefEdit可选择用户选择单元格,并返回该单元格对象。它与最后一参数为8时功能相同。
附件控件
除默认控件外,用户还可以调用附加控件以强化窗体的功能。事实上很多有用的控件都没有在工具箱中罗列出来,需要用户手工调用。添加附件控件的步骤如下:
(1)在显显示窗体的前提下,单击菜单【视图】\【工具箱】;
(2)在工具箱右上角空白区单击右键,并选择【新建页】菜单;
(3)在空白区单击右键,选择【附加控件】,在“附件控件”对话框中将需要的控件打勾,然后单击“确定”按钮,工具箱的“新页”中将产生新加的控件。图中即包括Flash动画控件、多媒体控件和网页控件三个附加控件。
图 在新页中添加附件控件
设置控件属性
默认状态的控件不利于用户使用,所有控件都需要对其部分或者所有属性进行设置,才能使窗体更美观,同时也让用户更易掌握各控件的作用。
调窗体控件位置与大小
控件插入窗体中后,根据需要会对其大小与位置进调整。调整大小时可以选择该控件,并用右键按住其四周的九个控件点之一向任意方向拖动,直到合适大小为止。而对按钮和标签这类控件还可以通过菜单【格式】\【正好容纳】来使大小刚好与显示的文字宽度与高度一致,类似于批注框的AutoSize属性。
调整位置也和调整大小一样,可以分手工拖动和菜单工具两种方式。手工调整位置即选择对象后随意拖动,甚至可以拖到窗体边缘直到完全不看到控件。而菜单方调整方式没有手工调整的任意性,却可以使控件按一定的方式对齐。例如菜单【格式】中产子菜单【水平间距】、【垂直间距】、【窗体内居中】、【排列按钮】等等,读者可以逐个测试其对齐效果。
设置控件的顺序
当大个控件重叠时,可以调置其顺序。例如窗体中有一个按钮和一个图象控件,如果选插入按钮,后插入图像控件,那么图像控件会覆盖按钮。如果需要将按钮移至图像控件之上,可以采用以下步骤:
(1)选择图像控件;
(2)单击菜单【格式】\【顺序】\移至底层。
当有超过两个控件重叠时,也可以对某个控件进行“上移一层”或者“下移一层”,菜单中有相应的功能按钮。
共同属性与非共同属性
当窗体中有多个控件时,它们总有部分共同属性。对于共同属性部分,可以一次性设置完成,例如窗体中有一个标签和一个按钮控件,那么背景色就是它们的共同属性。可以同时选择两个控件然后单击快捷键【F4】显示“属性”对话框,在对话框中将BackColor属性设置为绿色,那么选中的所有控件都会同时具有该属性,见图所示。
图 设置共同属性
对于非共同属性,只能逐个设置。例如按钮控件的Cancel属性。
设置颜色属性
很多控件都有颜色属性,包括背景色和字体色,而这两种属性的设置一致。现以设置ForeColor(字体颜色)为例演示属性设围起过程:
(1)选择窗体中的按钮;
(2)按下快捷键【F4】显示“属性”对话框;
(3)在“ForeColor”右边的方框单击,调出颜色设置选项,默认选项卡为“系统”;
(4)切换到“调色板”选项卡,在其中罗列了48种颜色,见图所示。单击红色方块,按钮的字体立即显示为红色,见图所示:
图 通过调色板设置字体色 图 修改字体色之后的按钮
如果需要在代码中指定颜色,而非手工设置,最后的办法是手工设置一次,然后将产生的颜色代码复制到代码中。例如本例中红色的代码为“&H000000FF&”,那么完整代码如下:
= &HFF&————“&H000000FF&”输入到VBE中后会自动变成“&HFF&”,但功能一致,不会产生副作用。注意在输入代码时不要用引号.
设置宽与高属性
所有控件都有高(Height)和宽(Width)属性。虽然高与宽属性可以利用鼠标拖动调整,但部分时候需要更精确,利用数字才是上策。
设置高与宽属性相较其颜色属性更简单,直接在方框中输入数字即可,例如100或者25,当回车后可以立即生效。而用代码来设置属性则可以使用以下语句:
= 120
设置Pictrue属性
命令按钮、复选框、切换按钮、框架、多页控件、图像控件都可以设置背景图片。
以命令按钮为例,设置背景图片步骤如下:
(1)选择命令按钮,使用快捷键【F4】打开属性对话框;
(2)Pictrue属性默认显示“(None)”,单击“(None)”会出现一个浏览按钮;
(3)单击该按钮,弹出“加载图片”对话框,从目录中选择JPG或者BMP图片后返回窗体界面,按钮的背景将是显示为该图片。当图片的形状与按钮不同时,自动综放适应按钮:
设置光标属性
光标属性指鼠标移过控件时是显示的鼠标形状。以两个按钮分别设置不同光标为例演示:
(1)在窗体中插入一个按钮,然后将其Caption属性设置为“确定”;
(2)按住Ctrl键的同时,用鼠标左键拖动按钮,从而实现复制一个按钮,并将其Caption属性修改为“取消”;
(3)选择“确定”按钮,单击“MouseIcon”属性右边的方框,从弹出的“加载图片”对话框中选择文件夹“C:\WINDOWS\Cursors”(假设用户使用的XP,系统盘为C),然后选择光标文件“”,也可以上网上载动画光标;
(4)选择“MousePointer”属性设置框,从下拉列表中选择“99-fmMousePointerCustom”;
(5)选择“取消”按钮,再选择“MousePointer”属性设置框,从下拉列表中选择“15-fmMousePointerSizeAll”;
(6)选择窗体的前提下使用【F5】运行窗体,将鼠标移向“确定”按钮时将显示图所示光标形状,而鼠标移过“取消”按钮时则显示为图所示光标。
图 设置按钮的Pictrue属性 图 手形光标 图 十字光标
本例文件参见光盘:..\ 第十五章\设置光标属性.xlsm
设置复合框
复合框与列表框相比其它控件在设置上更复杂,需要设置的项目也更多。现在通过复合框引用工作表中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的值,且自动产生列标题,见图所示:
图 利用复合框引用工作表中的数据
本例文件参见光盘:..\ 第十五章\设置复合框属性.xlsm
设置Flash动画
Excel集成了Flash控件,可以嵌入并播放Flash动画。但默认状态下该控件未出现在工具箱中,需要用户附件控件才可以调用。现演示附加Flash控件并设置其属性之步骤:
(1)在工具箱的右下角空白区单击右键,选择菜单【附件控件】;
(2)单击列表中的任意控件,然后按下“s”,再按下“h”键,光标将定位于“shockwave flash object”处,单击将该项目打勾。如果不存在该项目,那么请到网上下载Flash控件(.ocx格式);
(3)返回工具箱后在工具箱中将多出一个名为“shockwaveflash”的控件,图标为。单击该按钮并在窗体上拖放产生一个Flash控件;
(4)选择该Flash控件,然后进入属性对话框中,选择“Movie”属性设置框,并输入Flash动画的完整路径,例如“E:\第十五章\”;
(5)选择“EmbedMovie”属性设置框,从下拉列表中选择“True”,表示嵌入Flash到文件;
(6)手工拖动Flash,调整其大小和边距,然后单击【F5】执行窗体,在窗体中已经显示该Flash动画,见图所示:
图 在窗体中播放Flash动画
本例文件参见光盘:..\ 第十五章\播放Flash动画.xlsm
窗体与控件的事件
窗体与控件与工作表、工作簿一样,全都拥有自己的事件。通过事件可以让程序在某些条件下自动执行。
窗体事件介绍
窗体总共有20个事件,罗列如下:
表15-1 窗体事件列表
事件
触发条件
AddControl
当将控件插入到窗体、框架或多页控件中的一个页面中时
AfterUpdate
在通过用户界面更改了控件中的数据后
BeforeDragOver
当拖放操作正在进行时该事件发生
BeforeDropOrPaste
当用户即将在一个对象上放置或粘贴数据时
BeforeUpdate
控件中的数据被改变之前该事件发生
Change
当 Value 属性改变时该事件发生
Click
在下列两种情况下,发生该事件:
用鼠标单击控件;用户最终在几种可能的值中为控件选择一个值。
DblClick
当用户指向一个对象并双击鼠标时
DropButtonClick
每当下拉列表出现或消失时
Enter、Exit
一个控件从同一窗体的另一个控件实际接收到焦点之前,Enter发生。同一窗体中的一个控件即将把焦点转移到另一个控件之前,Exit发生。
Error
当控件检测到一个错误,并且不能将该错误信息返回调用程序时
KeyDown 和 KeyUp
按下和释放某键时这两个事件依次发生。按下键时发生KeyDown,而释放键时发生 KeyUp
KeyPress
当用户按下一个ANSI键时
Layout
当一个窗体、框架或多页改变大小时
MouseDown和MouseUp
用户单击鼠标按键时发生。用户按下鼠标按键时发生MouseDown;用户释放鼠标按键时发生MouseUp
MouseMove
用户移动鼠标时
RemoveControl
当从容器中删除一个控件时
Scroll
重新定位滚动块时
SpinDown 和 SpinUp
用户单击数值调节钮的向下或向左键时发生SpinDown。用户单击数值调节钮的向上或向向右键时发生SpinUp。
Zoom
当 Zoom 属性的值改变时
其它很多控件的大部分事件都与窗体的事件一致。
显示窗体时随机加载背景图
〖案例要求〗:显示窗体时随机加载同路径下“背景”文件夹下的图片。
〖过程代码〗:
Private Sub UserForm_Activate()
= LoadPicture( & "\背景\" & Int(Rnd() * 10 + 1) & ".jpg")
End Sub
插入窗体后,双击窗体进入窗体事件代码窗口,因窗体的默认事件是单击事件,那么它会自动产生“UserForm_Click”的事件代码外壳。删除该代码后录入以上“UserForm_Activate”事件过程的代码。该代码用于加载窗片,当激活窗体时执行该事件。而下面的代码用于显示窗体,代码需要保存在模块中:
Sub 显示窗体()
End Sub
〖注意事项〗:
1. 只有保存过的工作簿才有Path属性,所以使用本代码需要保存工作簿;
2.在本工作簿同路径下必须有“背景”文件夹。本例中10个图片按1、2、3……命名。
〖功能测试〗:
执行过程“显示窗体”,窗体会的自动显示图片背景。多次执行可以随机变化。
本例文件参见光盘:..\ 第十五章\随机背景图.xlsm
初始化窗体时填充列框下拉列表
〖案例要求〗:初始化窗体时,将第一个工作表A列中所有数据添加到窗体中的复合框。
〖过程代码〗:
Private Sub UserForm_Initialize()
Dim Item As Byte
'遍历Sheets(1)A列所有非空单元格,逐个添加到复合框
For Item = 1 To Sheets(1).Cells(, 1).End(xlUp).Row
Sheets(1).Cells(Item, 1).Text
Next Item
End Sub
〖注意事项〗:
1. 执行本过程前,需要在窗体中插入一个复合框,却该复合框保持默认名称“ComboBox1”,否则引用该对象时会产生错误。如果需要将默认名称修改为其它有意义的名字,代码也相应修改。“UserForm_Initialize”事件在初始化窗体时触发;
2. 也可以对RowSource赋值,从而实现一次性添加复合框的列表项目,代码如下:
= Sheets(1).Name & "!" & Intersect(Sheets(1).UsedRange, Sheets(1).[a:a]).Address
注意必须有“Sheets(1)”语句,否则它会引用当前工作表的数据。
〖功能测试〗:
按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”,当单击窗体中的复合框时,其下拉列表将引用A1的值,见图所示:
图 窗体初化时设置复合框的数据源
本例文件参见光盘:..\ 第十五章\窗体初化时设置复合框的数据源.xlsm
双击时关闭窗体
〖案例要求〗:双击窗体的非标题区关闭窗体。
〖过程代码〗:
Private Sub UserForm_DblClick(ByVal Cancel As )
Unload Me
End Sub
〖注意事项〗:
1. 本事件过程在双击窗体非标题区域时触发。如果在窗体中有其它任何控件,那么双击控件不会触发窗体的“UserForm_DblClick”事件;
2. Unload方法可以关闭指定认的窗体,而“Me”关键字代表当前窗体。
〖功能测试〗:
按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”,当双击窗体中标题区以外的任何空白区,窗体会关闭。
本例文件参见光盘:..\ 第十五章\窗体初化时设置复合框的数据源.xlsm
窗体永远显示在上左角
〖案例要求〗:让窗体永远显示在屏幕左上角,无法移动其位置。
〖过程代码〗:
Private Sub UserForm_Terminate()
= 0
= 0
End Sub
Private Sub UserForm_Layout()
= 0
= 0
End Sub
〖注意事项〗:
1.第一个事件是窗体初始化时触发,设置其左边距与上边距皆为0;
2.第二个事件是改变窗体位置时触发,总是强制位左边距与上边距为0.
〖功能测试〗:
按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”,窗体默认显示在屏幕左上角。当按下标题区域拖动窗体移动位置时,松开鼠标后窗体会还原位置。
本例文件参见光盘:..\ 第十五章\窗体永远显示在上左角.xlsm
按比例缩放窗体及滚动窗体
〖案例要求〗:将窗体进入放大或者缩小,窗体中的所有控件都都比例相应地缩放。当窗体放大到100%以上时,可以利用滚动条滚动窗体。
〖过程代码〗:
在窗体中插入一个标签(Label)、一个旋转按钮(SpinButton)和一个图像控件(Image),并对图像控件设置Pictrue属性,以方便观察窗体放大与缩小的变化过程。然后在窗体事件代码窗口输入以下代码:
Private Sub UserForm_Initialize() '窗体安能始化时设定转换控件的的最小值、最大值与当前值,以及设置标签控件的显示字符
= 10
= 400
= 100
= "缩放到:" & & "%"
End Sub
Private Sub SpinButton1_SpinDown() '缩小旋转按钮的值时将窗体也对应地缩小,且以标签上显示缩小的值
Zoom =
= "缩放到:" & & "%"
End Sub
Private Sub SpinButton1_SpinUp() '缩小旋转按钮的值时将窗体也对应地放大,且以标签上显示放大的值
Zoom =
= "缩放到:" & & "%"
End Sub
Private Sub UserForm_Zoom(Percent As Integer) '缩放窗体时触发,将标签的显示字符设置为与旋转按钮的值保持同步
= "缩放到:" & & "%"
If Percent > 99 Then '如果缩放比例大于99%则显示滚动条
ScrollBars = fmScrollBarsBoth
ScrollWidth = Width * Percent / 100
ScrollHeight = Height * Percent / 100
Else
ScrollBars = fmScrollBarsNone '否则不显示滚动条
End If
End Sub
'滚动窗体的动滚动条件时触发,当拉动滚动条时,将窗体的标题修改为滚动方向和滚动的值
Private Sub UserForm_Scroll(ByVal ActionX As , ByVal ActionY As , ByVal RequestDx As Single, ByVal RequestDy As Single, ByVal ActualDx As , ByVal ActualDy As )
= "滚动方向:X:" & RequestDx & " Y:" & RequestDy
End Sub
〖注意事项〗:
1. 窗体的所有事件中滚动事件和缩放事件较复杂,需要配合其它控件来完成。本例利用旋转按钮来将窗体缩小或者放大,且在标签上显示对应的缩放比例。
2.本例中窗体的缩放事件“UserForm_Zoom”根据缩有一天比例来控件滚动条是否显示;
3.本例中窗体的滚动事件“UserForm_Scroll”则在用户拖动滚动条时显示滚动的座标。
4.从本例中读者也可以看到,在窗体初始化时,可以利用事件设置控件属性,而非通过属性对话框手工设置。
〖功能测试〗:
1.按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;
2.默认状态如图所示。当按下旋按钮的下箭头时窗体中所有控件都缩小,且在标签上显示比例,见图所示:
图 默认状态 图 缩小到85%
3.再按下旋转按钮的上箭头,则将窗体中所有控件放大,放大114%时效果见图所示;
4.当超过100%时用户可以拖动滚动条来滚动窗体,使其显示超出窗本的部分,同时在窗本的标题栏显示滚动的座标,见图所示:
图 放大到114% 图 滚动窗体
本例文件参见光盘:..\ 第十五章\窗体缩放与滚动事件.xlsm
控件事件介绍
窗体中的任何控件都自己专用事件,但大部分事件窗体的事件在语法上一致。
限于篇幅,不再一一罗列各种控件所支持的事件,读者可以在帮助中查询所有控件的事件。例如复选框控件的事件,可以在帮助窗口输入“复选框控件”,然后选择“复选框控件”的帮助,再单击“事件”即可看到它所支持的所有事件。从列表中选择事件名可以查看详细解释与实例。
在窗体中建立超链接
〖案例要求〗:窗体中建立三个网址地链接,鼠标移过时显示下划线及蓝色标示,在窗本的标题显示网址,单击则打开该网址。
〖过程代码〗:
在窗本中插入三个标签,然后在本事件代码窗口中输入以下代码:
'鼠标移过时标签文字显示下划线并蓝色字体显示,窗体标题显示网址
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= True
= &HFF0000
= ""
End Sub
'单击时打开对应的网址
Private Sub Label1_Click()
Shell " """""
End Sub
Private Sub Label2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= True
= &HFF0000
= ""
End Sub
Private Sub Label2_Click()
Shell " """""
End Sub
Private Sub Label3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= True
= &HFF0000
= ""
End Sub
Private Sub Label3_Click()
Shell " """""
End Sub
'鼠标移过窗体时还原三个标签的状态
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= False
= &H0&
= False
= &H0&
= False
= &H0&
= "请选择网址"
End Sub
〖注意事项〗:
1. 鼠标移过标签后,标签的文字会变化及下划线显示,为了让鼠标离开时可以还原,需要配合窗体的“UserForm_MouseMove”事件来完成;
2.单击时打开网址需要对网址用半角双引号引起来,才能防止网址中有空格等特殊字符时不产生错误。在字符串中产生一个双引号“"”的方法是使用两个双引号并排“""”。
〖功能测试〗:
1.按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;
2.当鼠标在“Excel百宝箱论坛”上移过时,该标签将下划线且蓝色显示,同时在标题栏显示该论坛地址,见图所示。如果单击该标签文字,可以打开对应的论坛;
3.当移动鼠标到空白区后,标签将原还,且窗体的标题也显示为“请选择网址”。
图 鼠标移过标签时的效果 图鼠标离开标签时的效果
本例文件参见光盘:..\ 第十五章\设计超链接.xlsm
鼠标移过更新列表框数据
〖案例要求〗:工作表中有一班、二班、三班的学员表,现需要在窗体中通过鼠标移过单选按钮的方式显示不同班级的学员资料。
〖过程代码〗:
在窗体中添加三用单选按钮和一个列表框,然后输入以下代码:
'激活窗体时设置三个单选按钮的标题及窗体标题
Private Sub UserForm_Activate()
= [A1]
= [B1]
= [C1]
= "请选择班级"
End Sub
'鼠标移过单选按钮时更新列表框数据源
Private Sub OptionButton1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= Range([A1], Cells(, 1).End(xlUp)).Address
End Sub
Private Sub OptionButton2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= Range([B1], Cells(, 2).End(xlUp)).Address
End Sub
Private Sub OptionButton3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= Range([C1], Cells(, 3).End(xlUp)).Address
End Sub
〖注意事项〗:
本例中窗体、单选按钮和列表框等等控件都利用代码来设置其设置,从而侃鼠标移过时改变列表显示效果。虽然可以通过属性对话框来设置,但用代码设置更利于修改与维护。
〖功能测试〗:
按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;鼠标移过单选按钮时,列表框中将显示鼠标指向按钮标题所代表的班级的学员资料。
图 让列表框随鼠标移动显示不同班级的学员资料
本例文件参见光盘:..\ 第十五章\让列表框随鼠标移动显示不同班级的学员资料xlsm
让输入学号的文字框仅能录入6位数字
〖案例要求〗:为了规范法学号,要求从窗体中输入学号并导入到单元格。输入时必须是数字,而且必须是6位才能导入到单元格。
〖过程代码〗:
在窗体中插入一个标签,将其“Caption”属性设置为“请输入学号:”,然后添加一个文字框、两个按钮,按钮的“Caption”属性分别设置为“确定”和“关闭”。最后双击窗体并在代码窗口输入以下代码:
Dim OldText As String
Private Sub TextBox1_Change() '只允许文本框输入数字,CTRL+V也不行
Dim text As String ', Item As Integer
text = TextBox1
'逐一检查是否输入的数字,如果某字符不是数字还原为前面已输入的数字
For i = Len(OldText) + 1 To Len(text)
If Asc(Mid(text, i, 1)) < 48 Or Asc(Mid(text, i, 1)) > 57 Then TextBox1 = OldText
Next
'记录当前已输入的数字,没有一个数字时则赋空值
OldText = TextBox1
End Sub
Private Sub CommandButton1_Click()
'如果不等于6位则退出,否则将数字导出到单元格
If Len(TextBox1) <> 6 Then
Exit Sub
Else
ActiveCell =
= "" '清空文字框
'返回文字框等于用户继续输入下一个
(1, 0).Activate '激活下一个单元格
End If
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
〖注意事项〗:
1.检查输入的字符是否是数字,通常利用Asc判断它的字符码是否在48到57之间;;
2.虽然仅仅在运行按钮的单击事件时也可以检查文字框中是否是6位数字,但在输入阶断就进限制可以更有效率;
3. SetFocus使某个控年具有焦点。本例中表示光标定位于文字框。
〖功能测试〗:
1.按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;
2.假设活动单元格是B2,那么文字框中输簇578364并单击回车键将激活“确定”按钮,再次单击回车键,文本框中的学号输入导出B2单元格,然后激活B3、清空文字框,而且光标返回文字框等等用户录入下一个学号,见图所示:
图 将文字框的6位数字导入到单元格并返回文字框
本例文件参见光盘:..\ 第十五章\让文字框仅能录入6位数字.xlsm
鼠标拖动调整文字框大小
〖案例要求〗:设计一个带有两个文字框的窗体,用于输入个人简历和求职意向。但需要满足用户可以手动拖动的方式改变文字框宽度,从而适应简历字符的增减、变化。
〖过程代码〗:
在窗体中插入两个标签,做为文字框的标题;在标签下各插入一个文字框,让用户录入“自我介绍”和“求职意向”;在两个文字框中间插入一个标签,调整其高度为与文字框的高度一致,宽度为1/3厘米即可,然后将其Caption设为空,该标签用于拖动改变文字框的宽度;最后加添两个按钮,并将其“Caption”属性分别设置为“打印资料”、“关闭窗体”。双击窗体输入以下代码:
Private Sub UserForm_Activate() '激活窗体时设置三个标签和属性及窗体的标题属性
= "自我介绍": = "求职意向"
Caption = "应聘书"
= fmMousePointerSizeWE '设置第三个标题的鼠标指针为左右箭头
= "按下拖动可调整文字框宽度" '鼠标指向标签可以产生提示
End Sub
'鼠标移过做为分界线的标签时触发事件
Private Sub Label3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Button = 1 Then '如果按下了左键
With Label3
.BackColor = &HFF& '显示为红色,只有按下拖动时显示
'不允许将两个文字框中的任何一个宽度设为0,,所以预留左、右边距40
If .Left + X >= 40 And .Left + X <= Width - 40 - .Width Then .Move .Left + X
End With
End If
End Sub
'当释放鼠标时触发事件
Private Sub Label3_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Button = 1 Then '如果是左键
With Label3
.BackColor = vbButtonFace '还原标签颜色
, , .Left - 8 '调整第一个文字框宽度
.Left + 10, , Width - .Left - 20 '调整第二个文字框宽度和左边距
= .Left + 10 '调整第二个标题左边距宽度
End With
End If
End Sub
〖注意事项〗:
1. 本例中利用两个文字框中间的标签做为分界点进行宽度与高度调整,且鼠标移过该标签时有左右箭头,使程序显得极为专业。在添加控件时需要注意标签的宽度要刚好等于两个文字框中间的距离;
2.本例借用了第三有个标签做辅助,实现题目需求。然而也有取巧的办法,不需辅助控件,而且仅仅用一个窗体的鼠标移过事件就足够。代码如下:
Dim b As Long
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
On Error Resume Next
If Button = 1 Then '如果是左键
With TextBox1
b = .Width
If X > - 40 Or X < 40 Then Exit Sub
.Left, , X - .Left '调整第一个文字框宽度距
X + - (.Left + b), , - (.Width - b) '调整第二个文字框宽度和左边距
=
End With
End If
End Sub
〖功能测试〗:
1.按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;
2.将鼠标移动到两个文字框中间的部分将产生左右双箭头,同时提示用户拖动鼠标:
图 鼠标移过标签时显示提示
3.当按下左键并拖动时,第三个标签呈红色显示,并随鼠标移动,松开鼠标后,两个文字框的宽度随鼠标的位置而改变,见图:
图 移动鼠标后文字框的宽度变化
本例文件参见光盘:..\ 第十五章\鼠标调整文字框宽度.xlsm
为窗体中所有控件设置帮助
〖案例要求〗:在窗体中有很多控件,设计一个标签来显示所有控件的帮助信息,表示每个控件的用途。而该帮助信息由鼠标移动相应地变化,总是显示箭指向的控件的信息。而且该帮助信息需要一个开关来控件。
〖过程代码〗:
本例中涉及的控件较多,代码也较长,不再罗列窗体控件的添加方式。而且仅仅罗列出相与“设置帮助”相关的代码,其它代码就到光盘文件中查看。
'设置所有控件的鼠标移过事件,如果鼠标移过控件,而已勾选CheckBox1(显示帮助),那么在标签中显示该控件的帮助信息
'设置所有控件的鼠标移过事件,如果鼠标移过控件,而已勾选CheckBox1(显示帮助),那么在标签中显示该控件的帮助信息
Private Sub CheckBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "控制是否显示帮助,打勾时显示帮助,否则不显示"
End Sub
Private Sub CheckBox2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "打勾时列表框将显示单选框,否则不显示单选框。"
End Sub
Private Sub ListBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "用于显示学员资料,随时单选框相应地变化。"
End Sub
Private Sub OptionButton1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "单击时显示一班的学员资料。"
End Sub
Private Sub OptionButton2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "单击时显示二班的学员资料。"
End Sub
Private Sub OptionButton3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Then = "单击时显示三班的学员资料。"
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
= ""
End Sub
〖注意事项〗:
1.设计大中型插件时,为窗体中的控件指定帮助会显得更人性化。而该帮助信息只能通过鼠标移动事件来更新,当未指定向任何控件时应该显示为空;
2.为了体现灵活性,让不想显示帮助的用户有更多的选择空间,本例使用复选框来控件窗体的宽度,从而实现帮助信息的显示与否。
〖功能测试〗:
1.按所示方法在模块中添加一个“显示窗体”的Sub过程,然后执行“显示窗体”;
2.默状态下窗体显示为图所示,窗体中“显示帮助”选项呈未选中状态,窗体的宽度为150、而勾选“显示帮助”后,窗体会加宽度,在右边显示出鼠标箭头所指向控件的帮助信息。程序员需要为每个控件指定控件的用途、使用方式、执行结果等等信息做说明。图中鼠标指单选框“二月”,所以右边的帮助窗口则说明单击“二月”选项后的结果。
图 不显示帮助时的窗口状态 图 显示鼠标指向控件的帮助信息
本例文件参见光盘:..\ 第十五章\为控件没置帮助信息.xlsm
第十六章
窗体控件运用案例
窗体与控件在工作中应用极广,而对于插件开发来说显得犹为重要。
窗体与控件在应用中通常有两种应用方式:一是通过独立的窗体设计界面或者在窗体中进行演示、运算,二是与工作表中的数据产生交互功能,导入工作表数据或者将窗体中录入地数据根据需要导出到工作表。本章对两方面的应用进行案例演示,使读者对窗体与控件能更深入的理解。
本章要点:
窗体运用
窗体与表格的交互
窗体运用
窗体显示不能脱离Excel而存在,但却可以完全脱离工作表而独立运作。通常利用窗体设计验证界面、帮助窗口及某些图片展示等等,相对于工作表中操作会更灵活一些。
设计登录界面
〖案例要求〗:启动“产品介绍”的工作簿时模仿应用程序的Logo,设计一个登录界面。界面启动时只能看到登录界,当5秒钟后界面自动关闭时Excel程序才显示出来。中途按下【Esc】可以中断登录界面而直接进入工作表。
〖实现步骤〗:
(1)单击菜单【插入】\【窗体】,并将窗体的Caption属性设置为空;
(2)根据需要利用软件设计一张图片,通常包括公司名称、外景、地址、电话、传真等信息,然后将窗本的Pictrue属性设置为该图片的地址,表示将图片做为窗体的背景图;
(3)设计一个Flash动画(.swf格式),并在窗体底部插入一个Flash控件,将其“Movie”属性设置为Falsh文件的完整地址,且将“EmbedMovie”属性设置为True,表示将Flash文件嵌入到工作簿中;
(4)插入一个命令按钮,将其“Cancel”属性设置为True,表示允许按下快捷键【Esc】来执行该按钮的命令,然后将窗体拉宽,将该按钮拖动窗体边缘,再将窗体拉窄从而隐藏按钮;
(5)双击命令按钮,输入以下代码:
'按下Esc时关闭窗体,显示Excel主程序
Private Sub CommandButton1_Click()
Unload Me
= True
End Sub
'激活窗体时让设Flash的大小与位置
Private Sub UserForm_Activate()
= 0
= 398
= 52
= 204
End Sub
(6)插入模块,并输入以下代码,用于Excel启动时激活窗体,而在5秒钟后自动关闭:
'开启Excel时隐藏Excel主窗口,并显示窗体在5秒钟后执行过程“关闭”
Sub Auto_Open()
= False
0
Now + TimeValue("00:00:05"), "关闭"
End Sub
'显示Excel关闭关闭窗体
Sub 关闭()
= True
Unload UserForm1
End Sub
(7)保存工作簿,然后打开,在启动时将出现图所示登录窗口,而关闭窗口之前看不到Excel的主窗口;
图 自定义启动画面
(8)如果用户使用快捷键【Esc】键中途关闭,那么窗体关闭后Excel主窗口立即呈现出来;如果用户不理会,那么5秒钟后将自动关闭窗体,进入工作表界面。
本例中的登录窗口是模仿Excel的Logo设计的,但不可能做到与它一样。首先,无法屏弊Excel原来的Logo而只显示自定义的Logo,其次,是自定义窗体无法是Excel工作簿开启之前运行。虽然代码中隐藏了Excel主界面,但代码是地Excel启动后才执行,所以Excel的主窗口仍然会出现一定时间后才隐藏,然后显示自定义窗口。
本例文件参见光盘:..\ 第十六章\登录窗体.xlsm
权限认证窗口
〖案例要求〗:进入工作簿前进行权限验证。如果密码与用户名正确则允许打开工作簿,否则工作簿自动关闭。
〖实现步骤〗:
(1)插入一个窗体,将窗体的“名称”属性修改为“登录”,将其“Caption”属性修改为“权限验证”;
(2)为了让窗体更美观,可以设计一个背景图案,然后通过窗体的“Pictrue”属性将图片导入到窗体中;
(3)插入两个标签,并将其“Caption”分别修改为“用户名:”和“密 码:”;
(4)在标签后各插入一个文字框,用于输入用户名和密码;
(5)插入一个命令按钮,将其“Caption”属性设置为“确定”,双击“确定”按钮输入以下代码:
Private Sub CommandButton1_Click()
Static i '声腔明一个静态变量
If Len(TextBox1) = 0 Then MsgBox "用户名不能为空!", 64, "警告": Exit Sub
If i = 3 Then '如果错误三次
MsgBox "您已尝试三次错误,程序即将关闭!" '提示
Unload Me '关闭窗体
= True '恢复程序可见
False '关闭工作簿且不保存
Exit Sub '退出程序
End If
'只能andy/sky/andysky三个指定用户名可以登录
If (TextBox1 = "andy" Or TextBox1 = "sky" Or TextBox1 = "andysky") And Len(TextBox1) > 0 Then
If TextBox2 <> "admin" Then '如果密码不是admin
MsgBox "密码不符,请重新输入!" '提示
i = i + 1 '累加变量,记录错误次数
Exit Sub '退出程序
Else '否则
i = 0 '恢复计数器为0
Unload Me '关闭窗体
= True '显示程序
Sheets(1).Activate '进入第一个工作表
= xlInterrupt '恢复设置
End If
Else
MsgBox "用户名不符,请重新输入!", 64, "警告" '用户名不对则退出
End If
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode <> 1 Then Cancel = True '不允许单击关闭窗体的按钮
End Sub
其中第一段代码用于检证用户是否确入用户名与密码,如果未输入或者输入不是预设的三个用户之一、或者密码不正确,都会产生相应的提示,且等待用户继续输入。如果输入密码和用户名正确,则进入第一个工作表。
其中静态变量用于记录用户输入确误的次数,当错误三次后工作簿会自动关闭。
第二个过程用于禁用窗体的关闭按钮,防止用户手工关闭验证窗体。
(6)为了窗体可以正确启动,还需要在插入的模块中加入以下代码:
Sub auto_Open() '开启工作簿执行
= xlDisabled '禁止中断执行,即禁用快捷键【Ctrl+Break】
= False '程序不可见
登录.Show '菜单登录框
End Sub
以上过程表示工作簿启动时自动执行。且禁上用户使用快捷键【Ctrl+Break】来中断验证程序。
(7)保存工作簿后重新启动,将弹出图所示窗口,此时除输入用户名和密码外无法中断程序,也无法关闭窗口。
图 权限验证窗口
本例文件参见光盘:..\ 第十六章\权限验证.xlsm
设计计划任务向导
〖案例要求〗:设计分列或者数据透视图设计一个关于计划任务的向导。即在窗体中设计一个多页控件,每页进行一个方面的设置,最后根据窗体中的设置来完成一个任务。本例中的计划任务是在指定时间注销计算机或者关闭、重启计算机。
〖实现步骤〗:
(1)插入一个模块,并在模三维中录入以下代码,表示声明三个公共变量:
Public 时间类型 As Byte, 任务类型 As Byte, 时间 As String
(2)插入一个窗体,并将其“Caption”属性设为“计划任务”;
(2)在窗体中插入多页控件,并新建两页,将四页更命为“说明”\“时间”\“任务种类”\“执行”;
(3)返回第一页,插入一下标签,录入一些说明性的文字,表示本工具的用途;然后插入一个命令按钮,将其“Caption”属性设置为“开始—>”;
(4)双击命令按钮,输入以下代码:
Private Sub CommandButton1_Click()
= 1 '进入下一页(多页控件的第一页Value属性为0)
End Sub
(5)在第二页顶部插入标签,对时间选项作说明,详见附件;在下面插入一个分组框,在分组框中插入两个单选钮,将“Caption”属性分别设置为“相对时间”和“绝对时间”;在分组框右插入文字框,用于输入时间;最后插入一个命令按钮,将其“Caption”属性设置为“下一步”;
(6)双击“下一步”按钮,输入以下代码:
Private Sub CommandButton2_Click()
'如果输入了时间值
If () Then
= 2 '进入下一页
时间 = '将输入的时间赋与变量
End If
End Sub
'选择不同选项时对变量赋不同的值
Private Sub OptionButto1_Click()
时间类型 = 1
End Sub
Private Sub OptionButton2_Click()
时间类型 = 2
End Sub
第一段代码表示单击命令按钮时对变量赋值,并进入下一页;第二三段代码表示用户选择时间选项的单选按钮时记录选择的项目,如果是绝对时间则值1,否则为2;
(7)进入第三页,插入说明性的标签,以及三个单选按钮,将其“Caption”属性分别设置为“关机”、“重启”与“注销”;在右边插入一个命令按钮,将“Caption”属性设置为“下一步”;
(8)双击“下一步”输入以下代码:
Private Sub CommandButton3_Click()
= 3 '进入下一页
End Sub
Private Sub OptionButton3_Click()
任务类型 = 1
End Sub
Private Sub OptionButton4_Click()
任务类型 = 2
End Sub
Private Sub OptionButton5_Click()
任务类型 = 3
End Sub
(9)进入第四页,仍然插入一个说明性的标签,同时插入一个命令按钮,用于启动任务。将按钮的“Caption”属性设置为“启动任务”,然后双击按钮输入以下代码:
Private Sub CommandButton4_Click()
启动任务 'Call启动任务
Unload Me '关闭窗口
End Sub
(10)为了对窗体中的选项设置默认值,在窗体事件代码窗口输入以下代码:
'窗体激活时执行时
Private Sub UserForm_Activate()
= 0 '默认显示第一页
= fmTabStyleNone '不显示分组框的按钮
= True '默认为相对时间
= True '默认为关机
时间类型 = 1 '对变量设默认值
任务类型 = 1
End Sub
多页控件的“Style” 属性设置为“fmTabStyleNone”表示不显示标签,防止用户跳过中间环节而直接进入最后一页。
(11)最后到模块中输入以下两段代码,用于根据三个公共变量的值设置计划任务:
Sub 启动任务()
'如果“时间类型”变量为1时则在指定时间启动“任务”过程,否则时间值加上Now
If 时间类型 = 1 Then
TimeValue(时间), "任务"
Else
Now + TimeValue(时间), "任务"
End If
End Sub
Sub 任务()
'“任务类型”变量为1时关机,为2时重启,为3时注销
'其中Shutdown命令是Windows Xp和Vista的DOS命令,Win 98不支持。
Select Case 任务类型
Case 1
Shell "shutdown -s -t 1"
Case 2
Shell "shutdown -r -t 1"
Case 3
Shell "shutdown -l -t 1"
End Select
End Sub
“启动任务”过程首先判断变量的值,如果是1则Ontime的时间参数使用绝对时间,否则在时间值上加上“NOW”成为相对时间;“任务”过程也根据变量的值来决定任务的种类。如果变量为1则shutdown的参数为“-s”表示关机,变量显2时使用能数R表示重启,变量显3时则使用参数L,表示注销。其第二参数“-t”表示时间,以秒为单位,本例中设定为1秒钟。
(12)启动窗体,默认将显示第一页,见图所示。单击“开始”按钮可以直入第二页,默认选项为“相对时间”,保持为变,然后在时间框中输入“00:00:05”表示相对于现在5秒钟之后执行任划;
图 计划任务第一页 图 计划任务第二页
(13)进入第三页后默认选择为“关机”,单击“重启”表示将任务设置为5秒钟后重启。单击“下一步”按钮进入最后一页,再单击“启动任务”,在5秒钟后电脑后重启。
图计划任务第三页 图 计划任务第四页
本例中仅仅利用窗体与多页控件实现向导,而完全脱离工作表而执行。借用此思路,读者可以开发更多需要向导来执行多步骤的任务,
本例文件参见光盘:..\ 第十六章\设计计划任务向导.xlsm
设动动画帮助
〖案例要求〗:在窗体显示滚动文字,为插件提供帮助信息。假设为简繁互换插件设计帮助。
〖实现步骤〗:
(1)插入一个窗体,将其“Caption”属性修改为“简繁互换插件帮助信息”;
(2)利用第15章的知识在工具箱中添加一个网页控件,其全名为“Microsoft Web 浏览器”;
(3)在窗体中插入一个网页控件,且将控件的高度与宽度拖动到与本一致;
(4)双击控件打开代码窗口,并录入以下代码:
Private Sub UserForm_Activate()
Dim str As String
str = "简利繁转换工具箱 <P>功能说明:<P> 将选区中简体汉字转换成繁体,<P>" _
& " 也可以将繁体汉字转换成简体。<P>使用方法:<P> 选择待转换成的区域," _
& " <P> 然后单击菜单【简繁转换】<P> 在弹出的对话框中输入1表示简体转繁体" _
& " <P> 在对话框中输入2表示将繁体转简体<P> 单击确定后执行转换。" _
& " <P> 有建议请发送邮件到:<P> <a href='mailto:andy_qc@'>单击反馈意见</a>"
"about:blank"
"<html><head></head><body bgcolor=#00FF00 style='border:none;overflow:hidden;margin:0' oncontextmenu='return false'><p><marquee direction=up scrollamount='4'>" & str & "</marquee></p></body></html>"
End Sub
以上过程中“WebBrowser”是Web网页控件的名称,“about:blank”表示将网页初化为空白页。下一句中的“”方法则表示向网页中写入代码,相当于利用VBA编写网页。代参天中的“bgcolor=#00FF00”表示对网页设置背景色,可以随意修改。“direction=up”动画文字从下向上滚动,“crollamount='4'”表示滚动速度,用户可以根据多方面求调整。
网页中的“<P>”代表换行,对变量Str赋值时可以在任意地方插入“<P>”。
(5)执行窗体后,窗体中将产生绿色背景的网页,网页中的文字会从下向上滚动,见图所示。当窗体的文字滚动到最后一行时,会显示“单击反馈信息”,如果单击该字符串将启动Windows自带的邮件工具Outlook Express程序。
图 动画帮助窗口
本例文件参见光盘:..\ 第十六章\设计动画帮助.xlsm
用窗体浏览图片
〖案例要求〗:通过窗体浏览图片,允许用户随意选择图片存放的目录。
〖实现步骤〗:
(1)插入一个窗体,将其“Caption”属性设置为“图片预览”;
(2)在窗体左侧添加一个列表框控件;
(3)在窗体右侧添加一个图像控件;
(4)双击窗体进入代码窗口,并输入以下代码:
'窗体初始化时执行
Private Sub UserForm_Initialize()
Dim FileName As String, n As Long
'设置窗体与列表框的背景色
= &HFFFF80
= 16761024
FileName = Dir(PathStr & "*.jpg")
'记录Jpg图片个数
While Len(FileName) > 0
n = n + 1
FileName = Dir()
Wend
If n > 0 Then '如果文件个数大于0
= LoadPicture(Dir(PathStr & "*.jpg")) '将第一张图片加载到图像控年
FileName = Dir(PathStr & "*.jpg")
For I = 1 To n '逐个添加图片名到列表框中
FileName
FileName = Dir
Next I
Else '否则提示不存在并关闭窗体
MsgBox "选定的目录下不存在Jpg图片。"
End
End If
End Sub
Private Sub ListBox1_Click()
'将选择的文件名对应的图片文件加载到图像控件
= LoadPicture(PathStr & )
End Sub
第一个过程首先计算变量PathStr所代表的路径下是否存在JPG图片,如果没有则关闭窗体;如果有图片则将图片名称逐一添加到窗体中的图像控件。第二个过程用于单击列表框中不同文件名时修改图像控件中的显示图片。其中表示用户在列表框中选择的项目。
代码中的Dir用于获取名件名,在本书第19会进行详细介绍。
(5)为了让用户可以选择图片路径,需要在模块中输入以下代码:
Public PathStr As String
Sub 图片浏览()
Dim fd As FileDialog
'让用户选择路径
Set fd = (msoFileDialogFolderPicker)
'如果选择了文件夹则记录路径,否则退出程序
If = -1 Then PathStr = (1) Else Exit Sub
PathStr = PathStr & IIf(Right(PathStr, 1) = "\", "", "\")
'显示窗体
0
End Sub
以上过程中PathStr是工程级的公有变量,必须使用Public进行声明,否则窗体中无法调用该变量。
(6)保存工作簿,执行过程“图片浏览”,程序将弹出一个“浏览”对话框,当用户选择了存图片的文件夹后(光盘中准备了一个“图片”目录,里面有大量的JPG图片),会弹出“图片预临”对话框,默认显示用户选择的文件下第一个背片。当用户单击左侧列表框中的任意文件名时,右侧图像控件会显示相应的图片,见图所示。
图 利用窗体预览图片
本例文件参见光盘:..\ 第十六章\利用窗体预览图片.xlsm
窗体与表格的交互
前一节中介绍了窗体独脱离工作表的应用,但工作中更多的是窗体与表格实现交互应用。本节通过5个案例演示将数据导入窗体以及将窗体数据导入工作表。
设计多表录入面板
〖案例要求〗:当输入值时需要多个工作表中切换时,直接在工作表中输入是很不现实的,效率极低。例如图中,需要五个班学员的缴费,且收费时并非按班级统一执行,而是按学员到场的时间先后为序,那么就可能每输入一次切换一次工作表。而利用VBA的窗体设计一个输入面板,让程序自动查找工作表则会灵活许多。本例中假设收费标准为800,在输入学生的缴来学费后,自动将资料导出到相应的班级工作表中。同时查看缴费是否完整,如果其缴费低于800,则在欠费工作表中一一罗列出来,方便后续的统计。
图 收费表分布图
〖实现步骤〗:
(1)插入一个窗体,将其“Caption”属性修改为“资料输入面板”;
(2)在窗体中插入五个标签,并将“Caption”属性分别修改为“班级”、“姓名”、“性别”、“收到学费”与“欠费”;
(3)在“班级”右边插入一个复合框,用于显示班级名称;其它四个标签右边备添加一个文字框。特别需要注意的是文字框的顺序,因为录入数据后单击回车键时的跳转由这个顺序决定;
(4)最后添加一个命令按钮,将其“Caption”属性设置为“OK”。双击按钮录入以下代码:
Private Sub UserForm_Activate()
'激活窗体时显示五个班的班名,并设置默认值为一班
= Array("一班", "二班", "三班", "四班", "五班")
= "一班"
End Sub
以上过程表示激活窗体时将五个班名加入到复合框中,且设置复合框的默认值。
Private Sub TextBox3_Change()
If Not IsNumeric(TextBox3) Then TextBox3 = "" '输了的非数字则清空
If > 800 Or < 0 Then
MsgBox "请检查后再输入": TextBox3 = 0: '不在0到800之间则提示,并反返回Textbox3
Else
TextBox4 = 800 - '第四个文字框等于800已收到的学费
End If
End Sub
以上代码对输入学费的文字框进行限制,如果非数值则清除等待重新输入;如果不在0到800范围内则提示,且默认值设为0,最后将第四个文字框的值设置这为800减去已缴的学费。
Private Sub TextBox3_Exit(ByVal Cancel As )
'将焦点转移到OK按钮
End Sub
以上代码表示文学费文字框中单击回车键时将焦点转移到OK按钮,从而跳过欠费的文字框。
Private Sub CommandButton1_Click()'单击OK时执行
'如果未输入时禁用执行
If TextBox1 <> "" And TextBox2 <> "" And TextBox3 <> "" Then
With Sheets().Cells(, 1).End(xlUp)
.Offset(1, 0) = TextBox1 '将文字框中的值导入到单元格
.Offset(1, 1) = TextBox2
.Offset(1, 2) = TextBox3
.Offset(1, 3) = TextBox4
End With
Sheets().Select
Range("A2:D" & Cells(, 1).End(xlUp).Row). = xlContinuous
If > 0 Then Sheets().Cells(, 1).End(xlUp).Resize(1, 4).Copy _
Sheets("欠费人员列表").Cells(, 1).End(xlUp).Offset(1, 0) '如果不等于交费800则在将数据复制到欠费表中
Else
MsgBox "所有文本框不能为空!", vbOKOnly + 64, "提示"
End If
TextBox1 = ""
TextBox2 = ""
TextBox3 = 0
TextBox4 = ""
'进入复合框,等待选择班级
End Sub
以上过程在单击“OK”按钮或者在按钮具有焦点时单击回车键而触发。过程中首先检查三个文字框是否空白,若空白拒绝执行,否则将四个文字框的值按顺序导入复合框所指定的工作表中第一个非空行,而且将缴费低于800者复制到“欠费人员列表”中。最后清空四个文字框的值,将焦点移至复合框,等待用户处理下一笔资料。
(5)保存工作簿后,然后运行窗体,默认状态是显示一班,其它文字框为空。在列表框中可以利用键上的上下箭头来调节班级,当敲下回车后光标定位于姓名输入框。输入姓名后再回车则定位于学费输入框。在输入收到的学费时如果输入非数值会自动清空,如果输入负数或者大于800的值时程序会提示用户,等待重新输入,而欠费文字框中则会根据输入的学费自动计算欠费。当输入学费且敲下回车键时,光盘标会定位于“OK”按钮,单击回车键可以将刚才输入的所有数据导入到指定的班级中第一个非空行,见图。如果欠费大于0,还会自动将该笔资料复制到“欠费人员列表”工作表。
图 输入姓名、性别与学费 图 输窗体的数据导出到工作表
本例中例用窗体输入资料相对于直接在工作表输入资料有四个优势:不用在工作表间切换;不用使用鼠标而全凭键盘操作;不用人工计算欠费;当有欠费时不需要输入两遍,自动复制。
本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm
多条件高级查询
〖案例要求〗:工作表中有学生的姓名、成绩与学号,见图所示。要求任选其中作为查询条件,但对查询的目标需要罗列出其所有资料,支持模糊查询。
〖实现步骤〗:
(1)插入一个窗体,将其“Caption”属性设置为“成绩查询”;
(2)在窗体中添加一个复合框,在复合框右边添加一个文字框,用于输入查询条件,而下方再添加一个列表框,用于存放查找到的目标数据;
(3)双击文字框输入以下代码:
Private Sub UserForm_Activate() '激活窗体时设置复合框默认值
= Array("姓名", "成绩", "学号")
= "姓名"
End Sub
以上过程表示在激活窗体时设置复合框默认值
Private Sub TextBox1_Exit(ByVal Cancel As )
Dim arr(), RowCount As Integer, Item As Integer
Item = 1 '对变量赋值为1,目的是使数组第一列空白
For RowCount = 2 To Cells(, 1).End(xlUp).Row
'根据复合框的值决然定查找方式
Select Case
Case "姓名"
'如果是姓名,则以模糊查找方式对比姓名
If Cells(RowCount, 1) Like "*" & & "*" Then
Item = Item + 1
ReDim 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 If
Case "成绩"
If Not IsNumeric(TextBox1) Then Exit Sub
'如果复合框中选择成绩,那么文字框禁止输入非数字。
If Cells(RowCount, 2).Value * 1 = * 1 Then
Item = Item + 1
ReDim 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 If
Case "学号"
'如果是学号,也按模糊查找方式对比
If Cells(RowCount, 3) Like "*" & & "*" Then
Item = Item + 1
ReDim 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 If
End Select
Next RowCount
'如果变量大于1,则表示已查经查到目标,那么对数组的第一列添加标题
If Item > 1 Then
arr(1, 1) = "姓名": arr(2, 1) = "成绩": arr(3, 1) = "学号"
= 3 '默认显示3列,关设置其每列的宽度
= "50,30,60"
= (arr) '转置后赋与列表框
End If
End Sub
以上过程在文字框的“TextBox1_Exit”事件,表示在单击回车键后触发,从而可以减少一个“确定”的命令按钮。查找过程中区分三种方式,如果复合框中选择“姓名”,则按模糊查找方式将工作表A列的姓与文字框中的输入字符进行比较,相同则加入数组。最后将数组转置再导入到列表框,而成绩查询则只支持精确查找,学号查询支持模糊查找。
Private Sub TextBox1_Change()
End Sub
以上过程'表示文字框中输入新的查找对象时,清空列表框中上一次的结果。
(4)保存工作簿,运行窗体,复合框中保持默认值,单击回车进入文字框,输入“赵”,表示查找所有姓名中包含“赵”的同学的资料,图是查询结果。
如果将复合框修改为“成绩”,那么仅仅支持精确查找,图是成绩查询结果:
图 成绩表 图 以姓名为查询条件 图 以成绩为查询条件
本例文件参见光盘:..\ 第十六章\高级查询.xlsm
分类汇总捐赠额并按钮导出
〖案例要求〗:工作表中有捐款者姓名、款项与捐赠日期等资料,其中部分人捐赠多次,见图所示。现要求对捐赠者进行汇总,且可以按需求随意导入汇总结果。
〖实现步骤〗:
(1)插入一个窗体,将窗体的“Caption”属性设置为“汇总并导出结果”;
(2)在窗体中添加两个命令按钮,“Caption”属性分别修改为“汇总”与“导出”;
(3)在窗体下方添加一个列表框,用于存放汇总结果;
(4)
图 捐款表
本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm
奇偶行列选择工具
〖案例要求〗:
〖实现步骤〗:
本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm
背景着色工具箱
〖案例要求〗:
〖实现步骤〗:
本例文件参见光盘:..\ 第十六章\多工作表录入.xlsm
第十七章
表单控件与ActiveX控件
表单控件
控件的调出方式
表单控件功能一览
表单工具的优缺点
用单选框控制图表
用滚动条控制生产表数据
ActiveX控件
ActiveX控件功能一览
利用组合框突破数据有效性的单列限制
在工作表中显示Flash动画
在工作表左上角播放GIF运画
在组合框显示数据源的唯一值
入室篇:文件管理、菜单、API、VBE与加载项
第十八章
VBA命令处理文件
认识文件处理内置命令
Open与Close
Input#
ChDir与ChDriver
FileCopy
FileDateTime
FileLen
GetAttr与SetAttr
Kill
MkDir与RmDir
Name
文件操作案例
在D盘批量建立文件夹
判断文件是否存在
删除2009年1月1日以前的所有文件
罗列指定文件夹下隐藏文件
10分钟后文件自杀
删除D盘下所有空白文件
文件批量重命名
将当前工作表数据导出为TXT文件
第十九章
使用FileSystemObject和WScript
认识FSO
FSO定义与用途
FSO常用对象
FSO常用对象的方法
用FSO处理文件与目录
罗列D盘文件夹目录
在当前文件的父目录创建文件夹
查检E盘是否存在空目录
批量命名文件夹
创建文件夹文件并检查是否存在同名文件
每D盘下“生产表”文件夹备份
关于脚本语言WScript
关于脚本语言
WScript的常见对象
WScript的方法
脚本语言在文件管理中的运用
在桌面建立当前工作簿的快捷方式
将当前工作簿添加到收藏夹
将Excel 2003和2007添加到“发送到”文件夹
运行屏保程序
显示D盘“生产表”之目录树
显示D、E、F盘文件夹列表
在注册表记录当前工作簿打开次数
新建记事本且录入字符串
打开网上邻居
打开Excel选项之高级选项卡
罗列名称包括Excel的文件夹列表
自启动软件列表
罗列所有隐藏文件夹
第二十章
磁盘与系统信息管理
获取磁盘信息
FSO法
脚本法
DOS法
获取系统信息
罗列当前系统进程
计算机名与操作系统版本号
获取主板、显卡与硬盘信息
获取显示设置
获取网卡设置
获取CPU序列号
将“我的电脑”修改为实际名称
利用系统信息提升工作表安全
第二十一章
认识Excel的内置命令栏对象
关于内置命令栏
Excel对命令栏的处理方式
内置命令栏的分类
自定义快速访工具栏
了解CommandBars集合
CommandBars的常用属性
CommandBars的方法
获取CommandBars子对象的名称与类型
获取及保存内置图标
第二十二章
创建新工具栏
创建与删除工具栏按钮
建立工具栏基本语法
自定义新工具栏案例
控显示新工具栏显示方式
弹出式工具栏
什么是弹出式工具栏
创建一个弹出式工具栏
创建三级工具栏
特殊的工具栏(工作表目录、查找)
创建可读写的弹出式工具栏
利用工具栏文字框查找数据
切换零值、图像、分页符和批注的显示状态
工作簿标签设计
第二十三章
创建新菜单栏
菜单订制基础
菜单的分类
生成菜单基本语法
设计菜单注意事项
设计多级菜单
多级菜单基本思路
创建一个弹出式工作表菜单
让菜单适应Excel 2003和2007
可定制显示方式的菜单
设计感应菜单
在指定工作表才可用的菜单
工指定区域才可用的菜单
用一个菜单控制其它菜单的状态
选择图表才出现的菜单
第二十四章
操作快捷菜单
认识快捷菜单
快捷菜单的分类
不同快捷菜单的VBA表示法
Excel 2003和2007中快捷菜单的差异
定制快捷菜单
在右键中生成工作表目录
生成不受限的快捷菜单
快捷菜单的任意调用
在窗体中显示快捷菜单
第二十五章
认识类和类模块
类模块基础
类模块应用范围
类与类模块
类模块代码基本步骤
类的应用
新建工作簿时命名所有工作表(应用程序级别事件)
让零值、图像、分页符和批注切换工具提升为工作簿级
全自动转换单元格为首字母大写
开发一个颜色拾取器
第二十六章
API基础与API应用案例
API理论
API概述
认识DLL文件
API中的数据类型
声明API函数
API应用
获取计算机名和登录用户名
防PotoShop设计彩蛋
按任意地方都可拖动的窗体
设计圆形动画窗体
限制鼠标在窗体内移动
自由拖动改变窗体大小
渐进式出现与退出的窗体
第二十七章
VBA与注册表
VBA对注册表的控制方式
什么是注册表
VBA操作注册表的方法
VBA操作注册表的优缺点
借用脚本实现注册表的自由控制
注册表的应用
记录当前工作表最后一次打开时间
借助注册表限制工作簿使用次数
让程序自动调用上次的设置(零值切换)
第二十八章
VBE的对象模型与对象控制
准备工作
设置Excel选项
引用对象库
认识VBE的对象模型
VBE对象模型的层次结构
VBE对象介绍
VBE对象与Excel程序的关系
如何引用VBE对象
罗列当前工程中所有组件及其类型
VBE对象的控制
罗列指定模块中所有过程名称
计算代码总行数
利用代码添加/删除模块
用代码添加工作簿事件代码
用代码新建工作表且写入工作表事件
删除当前工作簿所有代码
导出当前工作簿所有VBA代码
用代码生成窗体与控件
第二十九章
VBE的高级运用
菜单定制基础
认识命令栏对象
生成菜单基本语法
罗列VBE中所有菜单与子菜单
生成菜单条与右键菜单
开发VBE插件百宝箱
开发插件的准备工作
开发代码编号工具
开发代码美化工具
开发代码清除工具
开发代码减肥工具
编写菜单
生成插件
第三十章
加载宏与加载项概述
关于加载宏
加载宏的特点
为什么使用加载宏
加载宏管理器
内置加载宏的加载与使用
安装自定义加载宏
关于加载项
加载项的分类
加载项的开发方式
两种加载项的安装方式
第三十一章
利用VBA编写XLAM加载宏
开发前的准备
xla与xlam的区别
生成加载宏的基本步骤
开发加载宏与普通VBA编程的区别
开发集公农历一体的日历输入器
确认程序需具备的功能
定义公历转农历的函数
设计日历输入器窗体
编写窗体初化代码
实现输入器与工作表交互
设计帮助
定制菜单
发布插件
第三十二章
利用编写COM加载项
COM加载项开发基础
安装VB 企业版
添加引用
开发COM加载项基本步骤
开发重复值制器
确认插件所需功能
建立VB工程
编写菜单代码及响应事件
编写重复值控制主程序
发布加载项并安装调试
攀峰篇:开发“Excel百宝箱”
第三十三章
程序开发思想
开发人员自我定位
区别开发人员与应用人员
开发人员基本条件
如何开发最佳应用程序
罗列应用程序需具备的功能
与终端用户交流
规划程序结构
设定友好的界面
提升程序通用性
第三十四章
开发“Excel百宝箱”
程序规划
了解终端用户需求
确认插件功能表
规划插件结构
安全工具箱
多工作表加密解密
设置允许编辑区
工作表反向加密
保护公式
禁用磁盘
财务工具箱
制作工资条头
根据工资计算所需钞票张数
小写金额转大写
大写金额转小写
工作簿
工作表拆分
工作簿拆分
复选框工具
文本与数据转换
打印工具箱
分页小计
打印当前页
双面打印
底端标题
合并工具箱
合并同行数据
合并数据并复制
取消区域合并填充原合并值
可还原的合并居中
合并列中相同数据
取消列中合并且还原数据
批注工具箱
批注管理器
添加个性化批注
建立图片批注
批量添加右列内容为批注
批量导入同名照片到批注
图表工具箱
批量修改标签
批量移动标签
图表输出为图片
图片工具箱
导出图形到硬盘
批量导入图片
单元格转换为图片
不重复值工具箱
提取唯一值
清除列中重复值
不能输入重复值
筛选唯一值
突出选区重复值
标示列中重复值
产生不重复随机数
文件工具箱
新建文件夹
新建工作表
查找文件并打开
批量重命名
建立文件目录
系统工具箱
锁定屏幕
查看电脑使用时间
查看磁盘信息
网卡IP与CPU的ID号
查看程序使用端口
清理垃圾文件
选择工具箱
行列选择工具箱
选择本表图片
区域定位工具
反向选择
程序员工具箱
生成系统图标
取得所有菜单ID
提取所有代码到工作表
删除所有VBA代码
一键修复Excel
其它工具箱
隐藏非使用区
生成字母与百家姓序列
一键删除超链接
一键删除工作簿数据链接
隔行插入行
选区字符统计
批量上标
七彩文字
工作表管理器
开发函数
开发函数
设计函数帮助
定制百宝箱帮助
定制底端标题帮助
定制百宝箱帮助
邮件返馈
定制多级菜单并发布
定制菜单
发布
测试
小结
1