VBA全名: Visual Basic Application
打开左上角的:文件->选项 弹出如下画面,选择“自定义功能区”,勾选上“开发工具”,点击确定。Excel的头上就会多出一个选项卡:开发工具。
另一种打开方式:
在excel头部的任意一个选项卡里单击右键,在弹出的菜单里选第三项“自定义功能区域”。也可以弹出如上画面。
因为很多病毒是靠VBA的宏传播,所以excel默认禁止执行宏,所以要执行VBA先要允许宏启动。在“开发工具”选项卡里点击“宏安全性”,选“宏设置”,选中“启用所有宏”。点击确定按钮,然后重启excel(关闭excle文件再重新打开)
点击“开发工具”中的“Visual Basic”,可以打开VBA编辑器。
VBA编辑器简称BE,即Visual Basic Editor
在资源管理器中并没有“模块”这样的目录结构,需要添加进去。在资源管理器中,右键单击,弹出菜单中选择“插入”,然后选择“模块”
双击资源管理器中的“模块1”,就可以在右边开发代码。
点击一个按钮,计算execl中的公式
1、点击“插入”,弹出表单控件,选中按钮控件
2、选中按钮控件后,在excel需要添加按钮的地方,拖动鼠标左键画出按钮,弹出设置按钮调用宏信息的top画面。给宏起一个方法名,然后点击“新建”按钮,创建代码。然后进入VBA编辑器。看到如下代码。而且在资源管理器中自动给我们创建了一个“模块2”对应此代码。
宏:在VBA中编写的一段小程序(Macro)
Cells(行,列) 单元格行列取值
编辑完代码后保存文件,会弹出提示,我们必须把文件另存为xlsm格式文件,否者编写的代码无法保存。
回到excel画面,点击按钮就会计算出A1+A2的结果,结果放在C1单元格中。
编辑按钮文字 -> 右键单击按钮选第四项“编辑文字”(也可以拖动按钮位置)
首先进入excle中的VBA编程界面,在顶部菜单栏中找到“工具”的按钮,点击“工具”,找到“选项”按钮,然后进入选项的编辑界面,点击“编辑器格式”,在右边可以选择字体的大小。
1、先在模块2中创建一个做减法的宏代码
2、然后新建一个按钮,在弹出的top画面中,把按钮关联到这个宏上。
这样点击这个新建的按钮,C1就会等于A1-B1
区别:
VBA的变量规则与java基本一致,最大的不同有以下两点:
1、VBA变量大小写不敏感 xy = XY = xY
2、当VBA程序中遇到一个新的变量名时,VBA会自动创建该变量,无需事先声明。
上面的代码x取一个固定单元格的值,程序根据单元格的值,计算对应行的数据。
注意:因为vba定义变量不像Java有变量初始化,Java程序一旦调用没有定义过的变量编译会出错。vba没有定义变量初始化,所以调用一个不存在的变量不会报错,他会自动创建这个变量,只是这个变量没有值。(所以把一个变量名修改后,后面调用这个变量的地方一旦漏改,程序不会报错,但是结果会出错)
为了应付没有定义变量调用也不会报错的情况,可以在程序里事先声明程序里只使用以下变量。
强制声明变量:
必须写在该模块文件的第一行写上Option Explicit,然后在方法体中用Dim声明,并用逗号分隔。一旦声明dim的方法体里使用了没有用dim声明的变量,编译就会报错。
不可以被修改的变量,一旦定义成了常量,修改这个值会报编译错误。代码写法如下:
Const p = 3.14
上述代码for循环是:i从11循环到20,步长是1(循环一次加1)
注意:For循环结束处的 Next i,i可以省略不写。(for循环套for循环的时候建议写,否者不知道那个结束end是属于哪个for循环的)
如果步长是每次增加1,step 1也可以省略不写。其他情况例如递减需要写成step -1。
(使用tab键让代码缩进关系统一)
第一种:
While Cells(1,2) <> “”
…
Wend
第二种:(常用)
Do While Cells(1,2) <> “”
…
Loop
例子代码:遍历每一个sheet页,计算每一个sheet页面的指定表格数据
1、基本用法
下面的宏关联按钮后,点击按钮会根据A1和A2单元格的值在A3中写入合格或不合格,以下就是if else的用例:
Sub ifElseTest()
Dim score1, score2
score1 = Cells(1, 1)
score2 = Cells(1, 2)
'当A1和A2单元格都大于60的时候,A3单元格显示合格
If score1 > 60 And score2 > 60 Then
Cells(1, 3) = "合格"
Else
Cells(1, 3) = "不合格"
End If
End Sub
2、如果判断语句写在同一行,那么可以不写End IF,例:
If score1 > 60 Then Cells(1, 3) = "合格"
3、ElseIf:(ElseIf 是一个关键字)
大于:> 小于:< 大于等于: >= 小于等于:<= 不等于: <> 等于:=
与:And 或:or 非:not
字符串连接用&符号,记得字符串用&连接时,字符串与&符号之间一定要有空格,否者会引发歧义。
str1 = "a"
str2 = "b"
Cells(2, 1) = Cells(2, 1) & str1 & str2
1、设置断点:直接在vba编辑器中找到要调试的代码,点击左侧竖栏,生成断点。运行程序代码会到此处停下。
2、单步执行 F8 (VBA编辑器点开“调试”,里面能看到所有调试方法和快捷键)
3、添加监视:
4、报错自动定位:
如果运行时代码出错,VBA会弹出提示出错,点击弹出框的“调试”,会自动定位到出错行
Sub setCellsStyle()
'把A1到C2之间(矩形的左上角与右下角范围)所有单元格字体变为红色
Range("A1:C2").Font.Color = -16776961
End Sub
根据上例发现,Excel中每一个元素的操作都有对应的对象,例如Cells就是操作单元格的,Range就是范围操作单元格的,对应的excel中各种图形等都有固定的调用方法,但是我们不可能记住每一种图形的调用对象,所以我们可以利用excel录制宏的方法操作格个图形对象,然后看录制宏的代码,就知道了每种对象如何调用。
Excel提供了一种记录我们操作excel动作的“录制宏”功能,只要我们记录住操作excel的动作,再执行录制的宏,excel就会重复我们录制的动作。我们可以查看excel生成的代码,了解这些宏如何操作控件,从而根据这些代码来完成我们的代码。
例:我们想知道删除行的代码如何操作
1、点击录制宏
2、弹出我们要录制的宏定义,可以直接点击确定
3、我们删除一行代码,然后点“停止录制”
4、发现我们的代码里生成了一段这样的代码
Sub 宏2()
' 宏2 宏
Rows("4:4").Select
Selection.Delete Shift:=xlUp
End Sub
红色部分就是删除行的代码,可以解释为:删除第四行(从第四行到第四行)
5、这样我们就可以开发一个程序,把第一列单元格内容小于10的进行行删除。
先建立一个按钮,关联宏方法deleteRow()
Sub deleteRow()
'从20行遍历到第1行(删除应该倒序遍历,因为index有变化)
For i = 20 To 1 Step -1
If Cells(i, 1) <= 10 Then
'删除第i行
Rows(i & ":" & i).Select
Selection.Delete Shift:=xlUp
End If
Next i
End Sub
VBA也把excel的各种控件分装成了对象,可以调用这些定义好的类、属性、方法操作控件。
这里的类、属性、方法与Java在概念上一致。
以下是调用操作excel常用的类:(当这些类被创建对象,每个对象就代表具体的excel相应控件)
Application :正在运行的excel系统本身
WorkBook :代表一个打开的excel文件也成为工作簿 (打开的一个excel文件就是一个工作簿)
WorkSheet :代表一张工作表,也是一个sheet页
Range :代表一个或多个单元格组成的内容区域。
因为一个application中有多个工作簿,一个工作簿(WorkBook)中有多个工作表
(WorkSheet),一个工作表包含多个单元格。
所以Application类里有一个属性是WorkBooks,他是一个集合,用来存放多个WrokBook。
WorkBook下也有一个属性,是一个集合,用来存放工作表,这个属性就是WorkSheets。
WorkSheet下也有一个属性,用来存放一个单元格对象,属性是Cells
WorkBook 工作簿 |
Application Excel系统 |
WorkSheet 工作表
|
Range 单元格内容区域 多个单元格对象 |
WorkBooks 存放WorkBook的集合
|
WorkSheets 存放WorkSheet的集合
|
Cells 一个单元格个对象
|
1、定义一个变量,变量类型是WorkSheet,写法如下:
Dim w1 As WorkSheet
2、给一个属于复杂类型的变量赋值,需要加关键字Set,写法如下:
Set w1 = WorkSheets(i);
例子:
Sub test1()
'定义一个变量存放sheet对象
Dim w1 As Worksheet
Dim i
'遍历所有sheet页
For i = 1 To Worksheets.Count
Set w1 = Worksheets(i)
'让每个sheet页面的10行1列都等于自己的sheet名
w1.Cells(10, 1) = w1.Name
Next i
End Sub
WorkSheet的Add方法:在所属工作簿的文件中新建一个工作表(sheet页)
新建工作表例子代码:
Sub test2()
'定义一个变量存放sheet对象
Dim w1 As Worksheet
'在本工作簿中新建一个sheet页
Set w1 = Worksheets.Add
'设定新建工作簿名
w1.Name = "new sheet"
End Sub
1、按sheet页所在顺序取工作表对象:
例:取第二个sheet页
Dim w1 As Worksheet
Set w1 = Worksheets(2)
2、按sheet页名字取工作表对象
Dim w1 As Worksheet
Set w1 = Worksheets(“new test”)
过程调用用Call关键字,例:
Sub test3()
Dim w1 As Worksheet
Set w1 = Worksheets.Add
w1.Name = "new"
End Sub
Sub test4()
'调用过程test3
Call test3
End Sub
Call关键字可以省略
函数是过程的一种,在执行结束后能将运行结果返回给调用者
函数需要不用Sub关键字,用Function,函数追后一行写上:函数名 = 要返回的值
这样调用这个函数的过程可以通过变量接受这个函数。
函数可以在excel的单元格中当成公式使用
在单元格中可以写入公式:
1、Len(str) 返回字符串长度
点三角箭头运行,弹出str字符串的长度
2、Trim(str) 去除两边空格
3、Replace(str,a,b)把字符串str中所有a都替换成b
4、Lcase(str) 所有英文字母都编程小写
Ucase(str) 所有英文字母都编程大写
5、Left(str,3)把字符串str从左边取3个字符
Right(str,3)把字符串str从右边取3个字符
6、Mid(str,2,5) 把字符串str从第二个字符开始,取五个字符
7、InStr(str,”a”) 在str中寻找a出现的位置,如果没找到返回0
InStr(3,str,”a”) 在str中,从第3个字符开始寻找a出现的位置
1、WorkBooks.open(文件名) 指定路径打开excel文件
2、Close关闭操作的工作簿
上例代码最后加入如下代码:
wb.Close
3、新建一个工作簿
WorkBooks.Add
Cells是定位单元格用的,并不是单元格对象,单元格对象类是Range
Cells的写法是我们平时的简写,如果写全应该是如下格式:
1、Range属性
返回任意单元格范围:Range(“D5”) , Range(“B3 : F7”)
也可以用下面方法定位左上角和右下角位置,定位到单元格范围:
2、修改Range范围内单元格内容
3、清除范围内单元格内容
例子代码:
4、Range的Font属性
还有color 字体颜色 Bold字体粗细 等属性
例子代码:
5、Range.Interior属性:
6、clear清除
7、Range.Merge合并单元格:将Range范围内单元格合并为一个单元格
Range.UnMerge拆分单元格: 将Range范围内单元格拆分为一个个单元格
以上代码可以用with改写为下面的代码:
1、默认直接调用的属性是Application的属性
代码中我们控制一个单元格常常这么写:
Cells(3,5) = “text”
那么Cells是谁的属性呢?如果我们用以下写法:
Dim w As Worksheet
Set w = Worksheets(1)
w.Cells(3,5) = “text”
那么我们知道Cells是Worksheet的属性,而直接写Cells,那么他是Application的属性
等同于:
Application.Cells(3,5)=”test”
Application代表所有打开的excel工作表里,当前正在被我们编辑的工作表(sheet),也可以理解为当前正在显示的工作表。Application可以默认不写。
同理直接调用的这个代码:Range(“E3”)=7,这个Range属性也是Application下的属性。
2、Application.ActiveWorkbook:
当前正处于激活状态的工作簿(即活动工作簿)对象
3、Application.ActiveSheet:
当前正处于激活状态的工作表(即活动工作表)对象
例:记录我们当前工作簿的对象,在操作变化后,能找到当初的对象
Dim w1 As Workbook, w1 As Wrokbook
Set w1 = Application.ActiveWorkbook
Set w2 = Workbooks.Add
w2.Wroksheets(1).Cells(2,2) =”a”; ‘操作新建工作簿的第一个工作表的2行2列单元格个
w1.Wroksheet(1).Cells(3,5) = “b” ‘操作当初工作簿第一个工作表的3行5列单元格个
以上这个代码的功能就是利用Application记住了当初的操作文件,即使操作其他文件后,也能够找到原来的操作文件。
4、Application.WorksheetFunction.公式名:
在VBA代码中直接调用Excel表格公式
例如:获取B2到D7单元格中最大的值
M = Application.WorksheetFunction.Max(Range(“B2:D7”))
也可以写成:
Set r = Range(Cells(2,2),Cells(7,4))
M = Application.WorksheetFunction.Max(r)
5、Application.DisplayAlerts
是否显示Excel警告框。
例如保存一个文件代码如下:
W2.SaveAs(“xxx.xlsx”)
W2.Close
执行第一次,很自然生成了一个xxx.xlsx文件保存到当前目录,但是再执行这个程序,每次都提示是否覆盖,用Application可以关闭这个提示,直接覆盖。
在代码中写入:Application.DisplayAlerts = false
再执行保存文件代码,文件直接覆盖,不再提示是否覆盖。
记得操作完后,在调用close后,一定要再让Application.DisplayAlerts = true,
否者excel任何操作都不在弹出提示信息。
6、Application.quit
退出excel
字符型:Dim str As String
str = “TEST”
整数型:Dim strLen As Integer
strLen = 3
变体型:不定义类型,根据赋值的数据类型定义类型(可以调整赋值来变化数据类型)
Dim str
str = “TEST”
变体类型缺点:
1、效率低,速度慢,因为程序每次都要判断这个数据是什么类型
2、一旦单元格被设置成文本类型,从单元格抓取数据,会把数字型当成字符型处理
注意:Dim a ,b As String a是变体类型,b是整数型
Dim a As Integer, b As String a 和 b 都是整数型
+ 和 & 的 区别:
加号只能链接两边都是字符串的数据,而&可以连接字符型也可以连接数字型
其他类型:
Integer:占用内存2字节,不支持小数,取值范围:-32768 到 + 32768
Long:占用内存4字节,不支持小数,取值范围:-2147483648到 + 2147483648
循环工作表最好用Long类型数据
Double:占用内存8字节,支持小数,有误差
Currency:占用内存8字节,支持小数,固定4位小数,数据精确。
1、特殊符号代表数据类型
Dim result As Long
Result = 30000 * 3
上面这两行代码会报错,因为30000*3的结果会存放到临时空间然后再赋值给result,即使result是Long型,能装下这个结果,但是临时空间是Integer,所以临时空间报错。之所以临时空间是Integer型,是因为30000和3都是Integer型数据,所以临时空间会变为Integer型。所以我们需要让临时空间知道30000需要定义成Long型,写法如下:
Dim result As Long
Result = 30000& * 3
Dim a&, b# 等价于 Dim a As Long ,b As Double
所以&作为连接符号的时候必须与左右两边留有空格,否者会被当成Long类型
2、下划线:
把很长的语句拆分成若干行书写
3、冒号
把很多行语句拼接到同一行,不浪费空间,代码整洁。
a1 = Cells(1, 1): a2 = Cells(1, 2): a3 = Cells(1, 3)
4、^ 幂运算
a的6次方 a ^ 6
5、 \ 只保留整数部分的除法符号
例:日期格式默认 月日年 时分秒,日期两边要有#,声明式日期
Dim d1 As Date
d1 = #1/19/2016 10:10:01 AM#
MsgBox d1
1、Date函数 显示日期
Dim d1 As Date
d1 = Date
MsgBox d1
2、Time()函数 显示时分秒
3、Now()函数 显示日期+时间
4、解析时间
5、DateDiff函数
6、DateAdd函数
注意:
日期型本质上就式一个Double型的数字,0代表1899年12月30日0时0分0秒
整数部分增减1就式增减1天,小数部分0.1代表0.1天,即2.4小时。
只返回True和False
逻辑关系运算符:
AND 并且 相当于Java的 &&
OR 或则 相当于Java的 ||
NOT 非 相当于Java的 !
其他
靠条件控制,不再进行下一次循环
直接跳出一个循环结构或一个子过程(函数),
比如退出Do While循环,可以写做Exit Do
如果是结束for循环就是 Exit For,退出函数用 Exit Sub
用Exit退出,Exit下面的代码将不再执行
注意:
1、Do While 可以用Exit Do结束循环,While无法使用Exit结束循环,所以处理循环不要用While
2、Exit 和Java中的break一样,当用在嵌套循环中,只能结束当前这个循环,不能结束所有循环。
直接跳转到指定标签位置:
因为GoTo语句太过随意,容易造成代码混乱,不建议使用,只建议在处理异常中使用,如下节
这个语句的意思是:从下一句开始,一旦发生错误就直接跳转到MyError标签处继续运行
从下面开始,如果某行运行错误,就忽略它并继续执行
验证数据是否是日期型,是日期型返回True
例如:验证单元格1行1列是否是日期型:
If IsDate(Cells(1,1)) = True Then
Cells(1,1) = DataAdd(“d”,38, Cells(1,1))
End If
如果x是某种数值类型,比如Integer,Long,Single,Double,Currency等,函数返回True,如果是其他类型返回False。
返回数据类型的类名
例如:TypeName(“test”) 返回String字符串
Cbool,Cdate,CStr,Cint,CLng,CDbl,Ccur,Csng,Cbyte,Cdec
VBA中可以自动类型转换,不加这些转换函数也可以,但是加了会让代码更加清晰。
如果Double转换成Integer会进行四舍五入,到那时他的这个四舍五入规则与我们的不同,用的是银行家舍入法:
Excel工作表中只能存放1900年以后的日期,如果想存放更之前的日期就需要用Cstr把日期型转换成字符型,然后存放到excel中。
VBA中的Round(x,n)也是用的银行家舍入法,对x进行四舍五入,并保留小数点后n位数字。
如果想使用我们平时用的规则,需要用excel中的公式:
Application.WorksheetFunction.Round(x,2)
如果不是excel文件中没有上面这个函数我们可以用Int()函数帮忙实现四舍五入的功能
Int(x)函数,返回一个不大于x的最大整数,例如:
所以用如下这个公式就可以实现四舍五入:
i = Int(x+0.5)
负数的四舍五入规则不一,本方法将-2.5舍入为-2,所以需要根据业务来确定是否符合规定
Asc(“A”) 把字符A转换成ASCII码
Chr(65) 把ASCII码转换成对应字符
所有的字符都可以转换成数字来表示,ASCII就是一种编码格式。
所以字符和数字可以靠上面的两个函数来回转换
用途:
1、如果需要在字符串中加入换行符,就需要用到上面的函数。例:
Str = “xxxxxxxxxxxxxxxxxx” & Chr(13) & Chr(10) & “xxxxxxxxxxxxxxxxxxx”
2、如果想把a到z打印到excel中,可以使用如下写法:
For i=65 To 90
Cells(i-63,2) = Chr(i)
Next i
我们想把数字转换成对应的字母,都可以用Char函数解决
3、比较字符大小,可以读取字符的每一个字母,用Asc函数转换成ASCII码,然后比较大小。
ASCII码转换表:
定义一个数组变量:Dim my_arr(9) As String
解释:
1、变量后面带括号代表是数组类型,括号里放数组最大下标,
2、数组下标从0开始,这里会放10个数
3、字符型数组
例:
自定义数组下标:
Dim arr (3 To 6)
让变体(因为这里没指定数组类型 )数组arr的最小下标为3,最大为6,改数组的元素包括 a(3) a(4) a(5) a(6)
Lbound(arr)返回数组最小下标
Ubound(arr)返回数组最大小标
以上两个函数的结果可以给for循环数组开始接受位置用
Split(待拆分的字符串,分割符字符串)
将字符串按照指定分隔符拆分成多个字串,返回一个数组
Dim arr() As String
arr=Split(Cells(1,1),”,”)
下面x代表数组每一个元素,a是数组
For Each x In a
str = x
Next x
VBA读取文件流程
1、打开文件
Open “d:\demo\client.text” For Input As #1
For Input 代表输入。#1代表代号,这个被打开文件的代号。
2、读取一行内容
Line Input #1,s (读取#1代表的文件一行记录,每执行一次这句代码就会读取一行,执行几次读取几行)
3、是否已读取到末尾
EOF(1) 读取#1代表的文件,如果读取到末尾这个函数返回True
4、关闭文件
Close #1 关闭#1代表的文件
例:
1、打开写入文本文件
Open “d:\demo\client.text” For Output As #1
写入一个文件,如果有同名文件会覆盖,没有此文件会创建
Open “d:\demo\client.text” For Sppend As #1
追加内容写入文件,不会覆盖原文件内容
2、写入一行
Print #1 “xxxxx”; 莫非带分号,再次写入,同行追加
Print #1 “xxxxx” 末尾空白,再次写入,字符会换行
3、关闭文件
Close #1
读取目录下所有文件
打开一个目录,目录结尾必须是“\”,f是返回的一个文件名
f = Dir("c:\testfile\")
然后每执行一次f = Dir都返回一个目录下的文件名
Sub test8()
Dim f As String
f = Dir("c:\testfile\")
Do While f <> ""
MsgBox f ‘弹出文件名
f = Dir ‘读取下一个文件名
Loop
End Sub
打开一个目录下所有excel文件转换成Wrokbooks对象:
Set w = Wrokbooks.Open(path & fileName)
读取指定文件
只想打开xlsx结尾的文件:
用这个判断语句 If Lcase(Right(fileName,5)) = “.xlsx”
更高级的写法:
fileName = Dir(“d:\test\*.xlsx”)
判断目录是否存在
判断文件是否存在
Dir(“目录文件名”)返回空字符串证明没有这个文件
返回目录和子目录下所有文件名:
只返回一层子目录
f = Dir(“D:\test\”,”vbDirectory”)
返回目录下所有文件和所有子目录文件:
需要用到递归方法
判断是文件夹:
GetAttr(“file dir”) And vbDirectory = vbDirectory
Range代表单元格区域对象 (可以理解为单元格二维数组)
1、Range.Row 该Range左上角单元格的行号
2、Range.Column 该Range左上角单元格的列号
3、Range.Address该Range各个对焦顶点的绝对引用地址。
Set r = Range(“B3:D9”) r.Address返回“$B$3:$D$9”
注意:
当Range包含多个矩形区域时,Row和Column只返回其中某一个矩形的左上角位置,并不一定是整个Range的左上角。
比如Range(“D3:E4,A1:B2”)的Row和Column返回的可能是第3行第4列,而非第一行第一列
1、Range.Count 该Range中的单元格数量
注意:Range对象由多个矩形区域构成时,个矩形相互重叠的单元格会被重复计算。
Count计算的其实是对象而非单元格个数,而且当数量太大时会发生溢出错误,所以计算单元格格最安全的方法是Range.Cells.CountLarge
2、Range.Rows 容纳了该Range中的每一行,可以使用Range.Rows(n)得到一个新的Range对象,代表该区域第n行的所有单元格。
注意:如果Range是由多个矩形区域构成,Rows只代表其中某一个矩形区域的所有行。
例子:Set a = Range(“C4:E12”)
a.Select ‘将Range对象全部选中(方便看现象)
Set rw = a.Rows(2) ‘把Range中的第二行全部取出,rw对象还是一个Range
3、Range.Columns 容纳了该Range中的每一列,可以使用Range.Columns(n)得到一个新的Range对象,代表位于该区域第n列的所有单元格。
4、Range.Cells(1,1) 取Range范围内的第一行第一列单元格
1、代表工作表(sheet)中全部单元格的对象
WorkSheet的属性Cells
取得最大行号:ActiveSheet.Cells.Rows.Count
2、代表工作表(sheet)中全部被使用过的单元格(有效单元格,包含所有数据)Range对象
WorkSheet的UsedRange属性
返回一个矩形区域的Range对象,正好能够容纳这个工作表中所有使用过的单元格。
注:即使一个单元格修改过格式没有填写内容也会被认为是被使用过,有些情况下单元格内容被删除清空后仍可能被认为是使用过的。
例子代码:把使用过的单元格选中
获取该行最后一行的行号:r.Row + r.Rows.Count -1 (左上角起始行号+Range范围行数-1)
如果我们的表格中有大量的单元格要读取,用平时我们用的Cells一个个单元格个读取效率将会非常低下,所以我们可以把要读取的单元格范围(Range)转化成二维数组,然后读取数组获得内容。
二维数组 Dim arr(3,5) As Long 有4行6列个元素(起始位置从0开始)
如果只想二维数组返回三行五列并且从1开始,可以写成
Dim arr(1 To 3, 1 To 5) As Long
Ubound(arr,1) 二维数组第1行数据的最大下标
Lbound(arr,1) 二维数组第1行数据的最小下标
把Range对象转换为二维数组的写法:
Dim arr()
arr=Range(“A2:C3”)
注意:Range转换成二维数组,数组的下标是从1开始(与Cells的下标规则一样)
例:
1、如果读取的范围操作Range的返回会发生下标越界错误
2、截取Range的数组,声明的时候必须是动态数组(数组后面的括号里不写下标范围)
3、这个数组必须是变体类型,不能声明成具体数据类型。
4、Range中即使只有一行数据,转化成数组后也是二维数组,是一个1行多列的二位数组。
把数组写入Range中
Dim s(2,3) As Integer
S(0,0)=1 : S(0,1)=2: S(0,2)=3: S(1,0)=4 : S(1,0)=5: S(1,0)=6
Range(“b2:e4”) = s
如果想把一个维数组放到execel的一列中,需要使用矩阵转置函数
Range(“c2:f2”) = Application.Transpose(s)
Range的结构可以理解成一个二维数组的结构,我们可以用双重循环遍历Range,如果我们不关心Range每个单元格的具体行号,只是单纯的遍历处理,那么我们可以用Each方式遍历Range的每一个单元格。
例:取出所有被使用的单元格,单元格个内容是红色的相加
Dim r As Range,r1 As Range
Set r = w.UsedRange ‘取出所有被使用的单元格
For Each r1 In r
If r1.Font.Color = vbRed Then
s = s + r1.Value
End If
Next r1
拆分成两个处理:(调用函数)
当Range是一个单元格时,此属性查看单元格中是否有公式,由公式这个属性返回True,没有返回False
当Range是一个单元格时,如果单元格是公式则返回公式文本,如果没有公式则与Value属性一样返回单元格的内容。
Range的Value属性,如果单元格是公式,Value返回的是公式的结果
如果想把一个单元格设置成一个公式,Formula和Value都可以:
当我们得到一个Range对象,要遍历Range的对象的时候下标是从1开始,无论这个Range的单元格在sheet中的什么位置。而Cells读取单元格的下标都是根据单元格相对于sheet中位置定的,必须是从sheet的1,1开始。
所以遍历Range,定义范围可以用如下方法
把多个Range对象联合成一个新的Range对象
Dim r As Range
Set r = Union(Range(a1:b2), Range(c1:d2), Range(e1:f2))
但是Range的区域如果有重合,新合并的Range会有重复单元格
找到同时属于多个Range的单元格(各Range重合的部分),作为一个新的Range对象返回。下图紫色部分就是找出的重合部分
返回一个包含了这个Range的最大连续使用区域。该区域与其他任何已使用单元格都不邻接。如下图通过蓝色部分找到蓝色区域所在表的所有单元格对象,返回一个Range对象。
例子:找到有“上海市”这几个字的单元格,把这个单元格所在的表变色
以该当前Range的左上角单元格为原点,生成一个指定大小的新的Range对象并返回。
上面两句可以合并写成:
按照Range的大小,根据指定的距离平行移动,从而得到一个新的同形状的Range。
选中Range的第一行数据
Dim r As Range
Set r = Range("A1:H100")
r.Rows(1).Select
选中当前sheet页的第二行数据
Dim r As Range
Set r = ActiveSheet.Rows(2)
r.Rows(1).Select
Range.Rows与Worksheets.Rows均可以一次返回多行或多列,例如
Rows(“2:3”) 返回第二行和第三行,返回的结果还是Range
Columns(“B:E”)返回B列到E列,返回的结果还是Range
如果D6到E7已经合并单元格,我们用For Each遍历这个Range对象
我们会发现他程序还是遍历了4次,而不是一次,程序还是认为Range中有4格单元格。但是只有第一个单元格D6有数据(合并单元格中的数据),其他单元格都没有数据。
结论:
1、多个单元格合并之后,仍被VBA认为是各自独立的单元格
2、第一个单元格的值为合并后显示的内容,其他单元格被认为是空值
Range.MergeCells属性
1、当该Range完全合并为一个单元格时,该属性为True
2、当该Range完全不包含合并单元格时,该属性为False
例如上例:Range(“d6:e7”).MergeCells = True
3、当该Range中有合并单元格,还有一部分没合并单元格,返回NULL
判断是否为Null的时候要用IsNull函数
4、把一个Range对象合并单元格:
Range(“A1:B3”).MergeCells = True
同理,解除合并单元格:
Range(“A1:B3”).MergeCells = False
5、VBA还提供了合并和取消合并单元格的方法。
Range.Merge合并单元格 例: Range(“A1:B3”).Merge
Range.UnMerge 取消合并单元格Range(“A1:B3”).UnMerge
Range(“A1:B3”).Merge True这个写法是按行合并单元格
Range(“A1:B3”).Select
让这个Range的对象都被选中(和被鼠标选中效果一致)
Dim r As Range
Set r = Selection
获取鼠标选中的单元格,返回一个Range对象
可以根据录制宏查看复制、剪切、添加等功能,结合鼠标选中(Select),做出很多操作excel的功能。
注意:尽量不要用Select获取Range对象,影响性能。可以用Cells()等方法代替Select
提高VBA代码的性能:
1、极可能合并不必要的Select和Selection
2、尽可能删除不必要的属性设置
3、尽可能减少对象中“.”的数量,用With消减“.”的调用
1、Optional 指定一个可选参数
Function mySumProduct(r As Rage, Optional useColumn As Boolean=False)
在调用此函数的时候,可以不写这个参数,不写这个参数默认False
IsMissing(a)函数
判断一个可选参数a是否被提供,如果没有提供,则返回True,否则返回False
要求:该可选参数必须是变体类型,且不能有默认值。
例子代码:
myFun(3, ,5)这个写法是只传递第一个和第二个参数。另外还有一个更好的调用方法
2、指定参数名传递参数:
使用 := 可以按参数名传递参数数值
如上例可以写成:myFun(a:=3, c:=5)
我们常用的Msgbox(弹出对话框)就是一个可选参数
Msgbox “信息!” , 1
第二个参数就是弹出画面的种类,只有确认按钮,有确认和取消按钮等
这些数字在VBA中有对应的常量,例如:
Msgbox “信息!” , 1 等价于 Msgbox “信息!” , vbYesNo
Msgbox还有第三个可选参数,是显示titile信息用的
Msgbox的返回值:
i=MsgBox(“please button”,vbYesNo)
在弹出的对话框上选yes,i返回6,选no,i返回7
调用函数决定是否写括号的三种情况:
1、没有参数:不写
2、有参数,调用语句处于一行代码中间:写
例1: x = myFun(3,4,5)
例2: If m myFun(3,4,5) >10 Then
3、有参数,调用语句独占一行代码:不写
例: Sub demo()
myFun 3, 5, 7
End Sub
之所以一行Function,不写括号是因为程序会把这个代码当成eval计算(同js的eval方法)
VBA对象的默认属性
4、如果记不住以上规则,可以在调用函数的时候加Call
Call mySub(3,5,7)
ByRef
VBA在默认情况下都是引用传递,如果把一个变量a传递给一个函数,函数对a进行了更改,函数结束后,a变量的值也会跟谁更改,因为VBA是把a这个对象传递给了函数。
ByVal
如果想让VBA值传递(也就是只把值传递给函数,函数即使修改了参数的值,也不会影响函数外面的变量)代码写法:Sub 过程名(ByVal a As Integer)
想把一个对象赋值给一个变量必须要用Set,想把一个基本类型赋值给一个变量就不用加Set
例如: Set w = Worksheets(1)
i = 5
以上就是VBA的默认属性导致
Cells(3,2)的默认属性是Value,所以我们想给这个单元格赋值的时候可以直接写Cells(3,2)=1。
当我们写 r = Cells(3,2)的时候,VBA不知道你是想把这个cells的Value赋值给r,还是想把这个cells对象赋值给r。所以对象型赋值要用Set标记。
MOD相当于java的%,求余数用
i=7 MOD 4 结果是3
取0 1 2 3的随机数: i=Int(Rnd()*4)
生成a到b之间的随机整数公式:int(Rnd()*(b-a+1)+a)
Rnd()计算的是一个伪随机数
生成没有规律的随机数需要如下操作:
用户在Excel中的一个操作,或者Excel系统自身的一个变化,都可以被视作一个“事件”。Excel会随时监听这些事件,并可以根据编码的要求,在某个事件发生时自动运行对应的VBA程序。 事件发生在哪里,代码就要写在哪里,如果事件是靠工作簿触发,事件代码就要写到工作簿文件中。 打开对应文件后,要起固定的方法名,例如想workbook一打开就触发事件,就要把代码写到ThisWorkbook这个文件里,而且方法名要叫 workbook_open() |
如下图,在文件中选中Worksheet,在右边就能看到所有对应sheet的事件名称。
工作簿常用事件
新增工作表(sheet)触发的事件用Workbook_NewSheet,sh这个参数代表刚刚新建的工作表。
例子代码:
新建一个工作表就自动在新建的工作表上增加一个表格:
这样没新建一个工作表,就会生成如下表格:
工作表常用事件
SelectionChange事件,每当用户选中一个新的单元格时,Selection_Change事件就会被触发。这个事件带有一个参数Target,代表刚刚被选中的单元格对象/区域。
例如:没选中一个单元格,就弹出这个单元格的坐标。
利用Range的属性:
Range.EntireRow Range所在单元格的整个行
Range.EntireColumn Range所在单元格的整个列
做一个功能鼠标选中这个单元格,单元格的所在行和所在列都改变颜色
效果如下,鼠标选中哪个单元格,单元格所在行和列都变为天蓝色。
注意:这个事件写在哪个sheet里,就哪个sheet页有这个事件,而不是所有sheet页都有这个事件。
Sheet事件文件与excel的sheet页一一对应,见下图:
所以多个sheet页都想要同一个事件效果,需要把实现效果的代码写在模块里,写一个子过程,然后每个sheet页都调用这个子过程就可以了。
变量和过程(函数)都有作用域
Private 只能被本模块内部的代码调用。
Public 可以被其他模块的代码调用。
子过程和函数如果指明,默认为Public
变量若不指明,默认为Private
例如:
Private Function test()
…
End Function
Private Sub Test2()
…
End Sub
Worksheet_Change(Target)
工作表事件,修改单元格内容,焦点离开,会触发。(没修改单元格内容也会触发)
如果我们在change事件发生后又修改了单元格内容,这样的操作又会触发change事件,然后就引发了事件级联,避免发生的办法如下:
下面例子是用Application.EnableEvents=False方法解决级联效应