我听说使用.Select
在Excel VBA中会被人们所反感,但我不知道该如何避免使用它。如果能够使用变量而不是Select
函数,那么我的代码将更加可重用。然而,如果不使用Select
,我不确定如何引用诸如ActiveCell
等内容。
我找到了这篇关于范围的文章和这个避免使用select的例子,但我找不到任何有关“如何”的信息。
我听说使用.Select
在Excel VBA中会被人们所反感,但我不知道该如何避免使用它。如果能够使用变量而不是Select
函数,那么我的代码将更加可重用。然而,如果不使用Select
,我不确定如何引用诸如ActiveCell
等内容。
我找到了这篇关于范围的文章和这个避免使用select的例子,但我找不到任何有关“如何”的信息。
使用定义过变量的Dim
Dim rng as Range
设置
变量为所需的范围。有许多方法可以引用单个单元格范围:
Set rng = Range("A1")
Set rng = Cells(1, 1)
Set rng = Range("NamedRange")
或者是一个多单元格范围:
Set rng = Range("A1:B10")
Set rng = Range("A1", "B10")
Set rng = Range(Cells(1, 1), Cells(10, 2))
Set rng = Range("AnotherNamedRange")
Set rng = Range("A1").Resize(10, 2)
您可以使用快捷方式调用Evaluate
方法,但这种方法效率较低,通常应在生产代码中避免使用。
Set rng = [A1]
Set rng = [A1:B10]
以上所有示例都是针对活动工作表上的单元格。除非你特别只想使用活动工作表,否则最好也声明一个 Worksheet
变量:
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Set rng = ws.Cells(1, 1)
With ws
Set rng = .Range(.Cells(1, 1), .Cells(2, 10))
End With
如果你确实想要使用ActiveSheet
,为了清晰起见最好明确指出。但要小心,因为一些Worksheet
方法会改变活动工作表。
Set rng = ActiveSheet.Range("A1")
再次强调,这指的是活动工作簿。除非你特别想只使用ActiveWorkbook
或ThisWorkbook
,否则最好也声明一个Workbook
变量。
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")
如果你确实想要使用 ActiveWorkbook
,为了清晰起见最好明确指出。但是要小心,因为许多 WorkBook
方法会改变活动工作簿。
Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1")
你也可以使用 ThisWorkbook
对象来引用包含正在运行代码的工作簿。
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1")
常见(糟糕的)一段代码是打开一本书,获取一些数据,然后再关闭。
这是不好的:
Sub foo()
Dim v as Variant
Workbooks("Book1.xlsx").Sheets(1).Range("A1").Clear
Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
v = ActiveWorkbook.Sheets(1).Range("A1").Value
Workbooks("SomeAlreadyOpenBook.xlsx").Activate
ActiveWorkbook.Sheets("SomeSheet").Range("A1").Value = v
Workbooks(2).Activate
ActiveWorkbook.Close()
End Sub
而且更好的方式是:
Sub foo()
Dim v as Variant
Dim wb1 as Workbook
Dim wb2 as Workbook
Set wb1 = Workbooks("SomeAlreadyOpenBook.xlsx")
Set wb2 = Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
v = wb2.Sheets("SomeSheet").Range("A1").Value
wb1.Sheets("SomeOtherSheet").Range("A1").Value = v
wb2.Close()
End Sub
将范围传递给你的Sub
和Function
作为范围变量:
Sub ClearRange(r as Range)
r.ClearContents
'....
End Sub
Sub MyMacro()
Dim rng as Range
Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:B10")
ClearRange rng
End Sub
你还应该将方法(如Find
和Copy
)应用于变量:
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10")
Set rng2 = ThisWorkbook.Worksheets("SomeSheet").Range("B1:B10")
rng1.Copy rng2
如果您要循环遍历单元格范围,通常最好(更快)的方法是先将该范围的值复制到变量数组中,然后再对该数组进行循环。
Dim dat As Variant
Dim rng As Range
Dim i As Long
Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10000")
dat = rng.Value ' dat is now array (1 to 10000, 1 to 1)
for i = LBound(dat, 1) to UBound(dat, 1)
dat(i,1) = dat(i, 1) * 10 ' Or whatever operation you need to perform
next
rng.Value = dat ' put new values back on sheet
这只是一个小小的展示,展示了可能性。
[A1:A10]
,rng1(12, 12)
也能够使用。 - MikeDRange
:ActiveSheet.[a1:a4]
或ws.[b6]
。 - Logan ReedSet
关键字。一个 variant
变量在你给它赋值之前不需要 Set
关键字。例如,Dim x: x = 1
是可以的,但是 Dim x: x = Sheets("Sheet1")
会生成错误 438。然而,为了混淆/说明,Dim x: x = Range("A1")
不会产生错误。为什么?因为它将对象的值赋给了变量,而不是对象本身的引用(因为它相当于 Dim x: x = Range("A1").Value
)。 - ashleedawg避免使用.Select、.Activate、Selection、Activecell、Activesheet、Activeworkbook等方法的两个主要原因
我们如何避免使用它?
1) 直接使用相关的对象进行操作
考虑以下代码:
Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"
这段代码也可以写成:
With Sheets("Sheet1").Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
2) 如果需要,声明变量。可以将上面的代码写成:
Dim ws as worksheet
Set ws = Sheets("Sheet1")
With ws.Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
这是一个很好的答案,但我在这个主题上缺少的是我们实际需要使用Activate的情况。每个人都说它不好,但没有人解释任何使用它有意义的情况。
以下是一些无法避免使用.Activate/.Select
的情况。(我会在发现更多链接时添加)
.Activate
Text To Columns
/ .Formula = .Formula
不起作用时,可能必须使用.Select
Sheets(2).[C10:D12].Value = Sheets(1).[A1:B3].Value
这样的代码更快地完成。 - robotik.Activate
来返回到原工作表,只需使用Application.Goto
即可。 - GMalc在之前给出的所有优秀答案中,我想要强调一点:
尽可能多地在你的VBA代码中使用命名区域(结合有意义的变量名),这可能是避免使用Select语句最大的事情。虽然上面提到了这一点,但它被略过了一点,不过它值得特别关注。
以下是使用命名区域的几个额外原因,虽然我肯定可以想出更多。
例如:
Dim Months As Range
Dim MonthlySales As Range
Set Months = Range("Months")
' E.g, "Months" might be a named range referring to A1:A12
Set MonthlySales = Range("MonthlySales")
' E.g, "Monthly Sales" might be a named range referring to B1:B12
Dim Month As Range
For Each Month in Months
Debug.Print MonthlySales(Month.Row)
Next Month
命名区域Months
和MonthlySales
包含的内容以及该过程正在执行的内容是非常明显的。
为什么这很重要?部分原因是为了让其他人更容易理解,但即使只有您会看到或使用您的代码,您仍应使用命名区域和良好的变量名,因为您会忘记一年后要做什么,并且您将浪费 30 分钟来弄清楚您的代码在做什么。
考虑一下,如果上面的示例被编写成这样:
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = Range("A1:A12")
Set rng2 = Range("B1:B12")
Dim rng3 As Range
For Each rng3 in rng1
Debug.Print rng2(rng3.Row)
Next rng3
一开始这段代码可能能正常工作,但是当你或者其他用户决定“哎呀,我想在列A
中添加一个新的年份列!”或者在月份和销售列之间添加支出列,或者为每个列添加一个标题时,你的代码就会出问题。由于你使用了糟糕的变量名称,因此要花费比预期更长的时间才能弄清楚如何修复它。
如果你一开始使用命名范围,那么Months
和Sales
列可以自由移动,你的代码仍然可以正常工作。
Range.Value
,它们只使用活动单元格和工作表。如果您不注意代码的位置或用户点击工作簿,则这也可能会有问题。'create and set a range
Dim Rng As Excel.Range
Set Rng = Workbooks("Book1").Worksheets("Sheet1").Range("A1")
'OR
Set Rng = Workbooks(1).Worksheets(1).Cells(1, 1)
或者你可以
'Just deal with the cell directly rather than creating a range
'I want to put the string "Hello" in Range A1 of sheet 1
Workbooks("Book1").Worksheets("Sheet1").Range("A1").value = "Hello"
'OR
Workbooks(1).Worksheets(1).Cells(1, 1).value = "Hello"
这些方法可以有各种组合,但对于像我这样的急躁的人来说,这就是简短地表达这个基本思想。
.Select
比直接引用单元格更好的选择,但我会为Selection
辩护,并指出它不应该因与.Select
相同的原因而被淘汰。Public Sub Run_on_Selected()
Dim rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each rng In rSEL
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Set rSEL = Nothing
End Sub
Public Sub Run_on_Selected_Visible()
'this is better for selected ranges on filtered data or containing hidden rows/columns
Dim rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each rng In rSEL.SpecialCells(xlCellTypeVisible)
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Set rSEL = Nothing
End Sub
Public Sub Run_on_Discontiguous_Area()
'this is better for selected ranges of discontiguous areas
Dim ara As Range, rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each ara In rSEL.Areas
Debug.Print ara.Address(0, 0)
'cell group operational code here
For Each rng In ara.Areas
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Next ara
Set rSEL = Nothing
End Sub
实际处理的代码可以是从单行到多个模块的任何内容。我使用这种方法来启动长时间运行的例程,对包含外部工作簿文件名的不规则选择进行操作。
简而言之,不要因为它与 .Select
和 ActiveCell
密切相关就丢弃 Selection
。作为工作表属性,它有许多其他用途。
(是的,我知道这个问题是关于 .Select
而不是 Selection
,但我想消除初学者 VBA 程序员可能会推断出的任何误解。)
Select
和 Activate
可以使您成为更好的 VBA 开发人员。通常情况下,当宏被录制时,Select
和 Activate
被使用,因此父工作表或范围始终被视为活动对象。Select
和 Activate
的方法:
源代码(由宏录制器生成):
Sub Makro2()
Range("B2").Select
Sheets.Add After:=ActiveSheet
Sheets("Tabelle1").Select
Sheets("Tabelle1").Name = "NewName"
ActiveCell.FormulaR1C1 = "12"
Range("B2").Select
Selection.Copy
Range("B3").Select
ActiveSheet.Paste
Application.CutCopyMode = False
End Sub
收件人:
Sub TestMe()
Dim ws As Worksheet
Set ws = Worksheets.Add
With ws
.Name = "NewName"
.Range("B2") = 12
.Range("B2").Copy Destination:=.Range("B3")
End With
End Sub
来自:
Sheets("Source").Select
Columns("A:D").Select
Selection.Copy
Sheets("Target").Select
Columns("A:D").Select
ActiveSheet.Paste
收件人:
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").Range("a1")
您可以使用[]
访问它们,与其他方式相比,这真的很美丽。自己检查一下:
Dim Months As Range
Dim MonthlySales As Range
Set Months = Range("Months")
Set MonthlySales = Range("MonthlySales")
Set Months =[Months]
Set MonthlySales = [MonthlySales]
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").[A1]
通常情况下,如果你想要选择(select)
某物,很可能你正在复制它。如果你只对值感兴趣,那么这是一个避免使用选择的好选择:
Range("B1:B6").Value = Range("A1:A6").Value
这可能是vba中最常见的错误。每当您复制范围时,有时会没有引用工作表,因此VBA会将错误的工作表视为活动工作表。
'This will work only if the 2. Worksheet is selected!
Public Sub TestMe()
Dim rng As Range
Set rng = Worksheets(2).Range(Cells(1, 1), Cells(2, 2)).Copy
End Sub
'This works always!
Public Sub TestMe2()
Dim rng As Range
With Worksheets(2)
.Range(.Cells(1, 1), .Cells(2, 2)).Copy
End With
End Sub
.Select
或.Activate
吗?.Activate
和.Select
。例如,无论在关闭文件时哪个工作表是活动工作表,你的Excel总是会首先选择封面工作表。因此,像下面的代码是完全可以接受的:
Private Sub Workbook_Open()
Worksheets("Cover").Activate
End Sub
另一个很好的例子是当你需要将所有工作表导出到一个PDF文件中,就像在这个案例中提到的 - 如何避免在VBA中使用选择/活动语句?
当一个命令只能与ActiveWindow
一起使用,比如ActiveWindow.Zoom或ActiveWindow.FreezePanes
.FillAcrossSheets
的结果决定的,因此这在VBA分类中处于某种中间状态(至少在我对VBA分类的理解中是这样)。 - VityataSub Macro1()
ActiveCell.Value = "foo"
End Sub
如果您想在非活动单元格(例如“B2”)中使用它,您首先应该选择该单元格,如下所示:
Sub Macro2()
Range("B2").Select
Macro1
End Sub
使用 Ranges 可以编写更通用的宏,用于将任何单元格的值设置为您想要的任何值:
Sub SetValue(cellAddress As String, aVal As Variant)
Range(cellAddress).Value = aVal
End Sub
接下来,你可以将 Macro2 重写为:
Sub Macro2()
SetCellValue "B2", "foo"
End Sub
并且宏1如下:
Sub Macro1()
SetValue ActiveCell.Address, "foo"
End Sub
始终指明工作簿、工作表和单元格/范围。
例如:
Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)
因为最终用户总是只会单击按钮,一旦焦点移开工作簿,代码想要处理的事情就会完全出错。
而且永远不要使用工作簿的索引。
Workbooks(1).Worksheets("fred").cells(1,1)
当用户运行您的代码时,您不知道会打开哪些其他工作簿。
.Activate
、.Select
、Selection
、ActiveSomething
方法/属性Selection
执行动作。.Activate
、.Select
、.Selection
、.ActiveSomething
方法/属性.Selection
或 .ActiveObject
。.Select
和.Activate
会很有用,可能会为用户节省一些时间和鼠标点击。例如,如果您的代码刚刚创建了一个全新的图表实例或更新了一个图表实例,用户可能想要查看它,您可以在其上调用.Activate
或其工作表以节省用户搜索它的时间;或者如果您知道用户需要更新某些范围值,您可以通过编程方式选择该范围。valOne = Range("A1").Value
同样地,如果您想要 'Sheet3' 的代码名称,可以将变量设置为该工作表的 Codename 属性。valTwo = Sheets("Sheet3").Codename
Select
和/或ActiveSheet
等等。这里有一个我找到的例子:https://dev59.com/6mEh5IYBdhLWcg3wHAND - Rick.Select / .Selection
。 - BruceWayne