VBA中很多的功能可以用内置的函数完成,其它的可以自己写函数完成。下面几点就是我在实战中遇到比较多的,而且带有一定困惑性的典型问题。
一、返回列的名字
这是个最简单的问题,但是有时候还是很需要的,方法比较简单,就是通过Address获取列的名字,比如“A”,“AB”等。
columnHeader = Left$(Right$(Target.Address, Len(Target.Address) - 1), InStr(1, Right$(Target.Address, Len(Target.Address) - 1), "$") - 1)
End Function
二、寻找实际使用的最后一行
这个问题在实际中经常遇到,而且实现的方式也多种多样。
使用ExecuteExcel4Macro实现
在Excel VBA中,内置函数ExecuteExcel4Macro用于执行一些Excel 4.0中的一些函数。其中有一个特殊的函数是返回Sheet使用的最后一行的,使用很简单,如下所示:
MsgBox ExecuteExcel4Macro("GET.DOCUMENT(10)")
End Sub
Application.ExecuteExcel4Macro的用法说明:
作用:执行一个 Microsoft Excel 4.0 宏函数,然后返回此函数的结果。返回结果的类型取决于函数的类型。
语法:ExecuteExcel4Macro(String)
参数:String,一个不带等号的 Microsoft Excel 4.0 宏语言函数。所有引用必须是像 R1C1 这样的字符串。如果 String 内包含嵌套的双引号,则必须写两个。例如,要运行宏函数 =MID("sometext",1,4),String 必须为 “MID(""sometext"",1,4)”。
返回值:Variant
说明:Microsoft Excel 4.0 宏不在当前工作簿或工作表的环境中求值。也就是说所有的引用都应该是外部引用,而且需要明确指定工作簿名。例如,要在 Book1 中执行 Microsoft Excel 4.0 宏“My_Macro”,必须使用“Book1!My_Macro()”。如果不指定工作簿名,此方法将失效。
示例:本示例对工作表 Sheet1 上的 C3 单元格执行 GET.CELL(42) 宏函数,然后在一个消息框中显示结果。GET.CELL(42) 宏函数返回当前窗口左边界到活动单元格的左边之间的水平距离。Visual Basic 中没有与此宏函数直接等价的函数。
Range("C3").Select
MsgBox ExecuteExcel4Macro("GET.CELL(42)")
使用End属性
在ExcelVBA中,使用End(xlUp)查找最后一行是最常使用且最为简单的方法,它假设要有一列总包含有数据(数字、文本和公式等),并且在该列中最后输入数据的单元格的下一行不会包含数据,因此不必担心会覆盖掉已有数据。但该方法有两个缺点:
(1) 仅局限于查找指定列的最后一行。
(2) 如果该列中最后一行被隐藏,那么该隐藏行将被视作最后一行。因此,在最后一行被隐藏时,其数据可能会被覆盖。但该列中间的隐藏行不会影响查找的结果。
LastRowInColumn = Range(Column & Rows.Count).End(xlUp).Row
End Function
使用Find方法
Find方法在当前工作有数据中进行查找,不需要指定列,也可以确保不会意外地覆盖掉已有数据。其中,参数LookIn指定所查找的类型,有三个常量可供选择,即xlValues、xlFormulas和xlComments。
(1) 常量xlFormulas将包含零值的单元格作为有数据的单元格。(当设置零值不显示时,该单元格看起来为空,但该参数仍将该单元格视为有数据的单元格)
(2) 常量xlValues将包含零值的单元格(如果设置零值不显示时)作为空白单元格,此时,若该单元格在最后一行,则Find方法会认为该单元格所在的行为空行,因此,该单元格中的内容可能会被新数据所覆盖。(在Excel中,选择菜单“工具”——“选项”,在打开的“选项”对话框中,选择“视图”选项卡,将其中的“零值”前的复选框取消选中,则工作表中的零值都不会显示)
如果在参数LookIn中使用常量xlValues的话,还存在一个问题是:如果您将最后一行隐藏,则Find方法会认为倒数第二行是最后一行,此时您在最后一行的下一行输入数据,则会将实际的最后一行的数据覆盖。
'使用常量xlFormulas,因为常量xlValues会忽略隐藏的最后一行
LastRow = Cells.Find("*", LookIn:=xlFormulas, SearchDirection:=xlPrevious).Row
End Function
Find方法中,参数LookIn的默认值为xlFormulas。
使用UsedRange属性
UsedRange方法可用于在工作表中已使用区域查找最后一行,该区域包括可能以前使用过的任何单元格,但现在其中的数据被删除了,比如目前的工作表中只有第1行至第5行共5行,其它行都无数据,但在第6行中有些单元格以前使用过(可能仅仅格式化或内容清除了,总之该行现在不含有数据),那么第6行也包含在该已使用的区域中。此外,如果最后一行被隐藏,那么会将因此,使用该方法查找最后一行是无规律且不可靠的,它通常可能会得到预料不到的结果。
With ActiveSheet
LastUsedRow =.UsedRange.Rows.Count + .UsedRange.Row - 1
End With
End Function
使用SpecialCells方法
也可以用SpecialCells方法实现查找最后一行,其常量xlCellTypeLastCell代表在”已使用区域”中的最后一个单元格,与UsedRange属性稍有不同的是,当您在最后一行中输入数据后,又将其删除,则此数据所在的单元格也包含在已使用的区域中,并且如果最后的行被隐藏,则将可见行的最后一行当作最后一行。
LastUsedRow = Cells.SpecialCells(xlCellTypeLastCell).Row
End Function
SpecialCells方法用于查找指定类型的值,其语法为SpecialCells(Type,Value),有两种主要的使用方式:
(1) 若参数Type仅考虑常量,则在查找时会忽略和覆盖由公式生成的任何数据。
(2) 若参数Type仅考虑由公式生成的数据,则在查找时会忽略和覆盖任何常量数据。
如果参数Type是xlCellTypeConstants或者是xlCellTypeFormulas,则Value参数可使用常量决定哪种类型的单元格将被包含在结果中,这些常量值能组合而返回多个类型,其缺省设置是选择所有的常量或公式,而不管是何类型,可使用下面四个可选的常量:
1) xlTextValues(包含文本); 2) xlNumbers(包含数字);
3) xlErrors(包含错误值); 4) xlLogical(包含逻辑值);
我们可以使用SpecailCells方法去找到其它特定类型的单元格所在的最后一行,下面是这些常量的一个完整的列表:
XlCellTypeAllFormatConditions (任何格式的单元格)
XlCellTypeAllValidation (带有数据有效性的单元格)
XlCellTypeBlanks (所使用区域中的空白单元格)
XlCellTypeComments (包含有批注的单元格)
XlCellTypeConstants (包含有常量的单元格)
XlCellTypeFormulas (包含有公式的单元格)
XlCellTypeLastCell (已使用区域中的最后一个单元格(看下面))
XlCellTypeSameFormatConditions (有相同格式的单元格)
XlCellTypeSameValidation (有相同数据有效性条件的单元格)
XlCellTypeVisible (工作表中所有可见的单元格)
使用CurrentRegion属性
Range对象的CurrentRegion属性返回代表单元格所在的当前区域,即四周有空行的独立区域,因此,可使用此属性查找当前区域的最后一行。但是使用其查找最后一行的一个缺点是,必须首先选取当前区域,然后进行查找。
三、自定义类型扩展
VBA中扩充基本类型的基本手段就是自定义类型,主要有两种方式。
1. 定义Type
使用Type关键字可以定义一些简单的自定义类型,这些类型使用起来就像基本类型一样使用,直接定义和赋值。例如:
Name As String
Address As String
Salary As Double
End Type
Dim Manager As Employee
Manager.Name = "Joe Smith"
Manager.Address = "123 Main Street"
Manager.Salary = 40000
Type定义和使用都很简单,但是它只是一种静态的数据结构,无法完成很多高级的功能;它的显著缺点如下:
(1).无法创建Type的实例,可以存放到数组中,但是无法存放到Collection与Dictionary中。
(2).无法控制成员的有效范围,也就是无法验证成员的值是合法的。
(3).只有数据,无法去定义行为。
2. 定义Class
正是由于Type简单但是难以胜任复杂的情景,所以当需要一些负责的处理数据的场景时,往往需要定义Class这种更强大的扩展。定义Class的方式也很简单,直接在工程中插入一个Class Module就可以了,可以在属性窗口中设置Class的名字。下面是示例:
Private pAddress As String
Private pSalary As Double
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
Public Property Get Address() As String
Address = pAddress
End Property
Public Property Let Address(Value As String)
pAddress = Value
End Property
Public Property Get Salary() As Double
Salary = pSalary
End Property
Public Property Let Salary(Value As Double)
If Value > 0 Then
pSalary = Value
Else
' appropriate error code here
End If
End Property
'使用例子
Dim Emp As CEmployee
Set Emp = New CEmployee
Emp.Name = "Joe Smith"
Emp.Address = "123 Main Street"
Emp.Salary = 40000
Class是更为高级的对象,它带来的良好的封装性,也带来了一定的复杂性。所以只应该对那些具有行为,重用性高的数据建模成Class。
Class需要使用New关键字去创建新实例,而且需要使用Set关键字去赋值。
使用Type经常会遇到(传递参数,存放到集合等等)的一个错误是"Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions."。这个时候,需要定义一个Class去完成相关功能,而不是去定义一个Type。