How to avoid using Select in Excel VBA
我听说过很多关于在Excel VBA中使用
我发现这篇关于范围的文章和这个例子关于不使用select的好处却找不到怎么样的东西?
一些如何避免选择的例子
使用
1 | Dim rng as Range |
1 2 3 | Set rng = Range("A1") Set rng = Cells(1,1) Set rng = Range("NamedRange") |
或多细胞范围
1 2 3 4 5 | 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) |
您可以使用
1 2 | Set rng = [A1] Set rng = [A1:B10] |
以上所有示例均指活动纸上的单元格。除非您特别想要仅使用活动工作表,否则最好还要使用
1 2 3 4 5 6 | 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 |
如果您确实想使用
1 | Set rng = ActiveSheet.Range("A1") |
同样,这指的是活动工作簿。除非您特别想要仅使用
1 2 3 | Dim wb As Workbook Set wb = Application.Workbooks("Book1") Set rng = wb.Worksheets("Sheet1").Range("A1") |
如果您确实想使用
1 | Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1") |
您还可以使用
1 | Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1") |
一个常见的(坏)代码是打开一本书,获取一些数据然后再关闭
这是不好的:
1 2 3 4 5 6 7 8 9 10 | 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 |
并且会更好:
1 2 3 4 5 6 7 8 9 10 | 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 |
将范围传递到
1 2 3 4 5 6 7 8 9 10 | 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 |
您还应该将方法(例如
1 2 3 4 5 | 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 |
如果您在一系列单元格上循环,通常更好(更快)将范围值首先复制到变量数组并循环遍历
1 2 3 4 5 6 7 8 9 10 | 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 |
尽管如此,这是一个很小的品尝者。
应该避免
我们如何避免它?
1)直接使用相关对象
考虑这段代码
1 2 3 4 | Sheets("Sheet1").Activate Range("A1").Select Selection.Value ="Blah" Selection.NumberFormat ="@" |
此代码也可以写为
1 2 3 4 | With Sheets("Sheet1").Range("A1") .Value ="Blah" .NumberFormat ="@" End With |
2)如果需要声明你的变量。上面的代码可以写成
1 2 3 4 5 6 7 8 | Dim ws as worksheet Set ws = Sheets("Sheet1") With ws.Range("A1") .Value ="Blah" .NumberFormat ="@" End With |
我将在上面给出的所有优秀答案中加上一个小重点:
您可以做的最大的事情是避免使用Select,尽可能在VBA代码中使用命名范围(结合有意义的变量名称)。上面提到了这一点,但有点掩饰;但是,它值得特别关注。
以下是使用命名范围的另外一些理由,但我确信我可以想到更多。
命名范围使您的代码更易于阅读和理解。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
命名区域
为什么这很重要?部分是因为其他人更容易理解它,但即使你是唯一一个会看到或使用你的代码的人,你仍然应该使用命名范围和良好的变量名,因为你会忘记你的意图一年之后,你将浪费30分钟来弄清楚你的代码在做什么。
命名范围可确保在电子表格的配置发生更改时(而不是!),您的宏不会中断。
考虑一下,如果上面的例子是这样编写的:
1 2 3 4 5 6 7 8 9 10 | 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 |
这个代码最初会正常运行 - 直到你或未来的用户决定"gee wiz,我想我要在Column
如果您已经开始使用命名范围,那么
我会给出简短的答案,因为其他人都给了很长的答案。
每当你录制宏并重用它们时,你都会得到.select和.activate。当你选择一个单元格或工作表时,它就会激活它。从那时起,无论何时使用非限定引用(如
因此,您可以通过直接引用您的单元格来消除这些问题。哪个:
1 2 3 4 5 | '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) |
或者你可以
1 2 3 4 5 | '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" |
这些方法有各种各样的组合,但对于像我这样的不耐烦的人,这将是尽快表达的一般想法。
"... and am finding that my code would be more re-usable if I were able to use variables instead of Select functions."
虽然我不能想到的只是一些孤立的情况,其中
有时候,通过点击几个键,分配给热键组合的短节省时间的宏子程序可以节省大量时间。当处理不符合工作表范围数据格式的袋装数据时,能够选择一组单元格来制定操作代码。与选择一组单元格并应用格式更改的方式大致相同,选择一组单元格来运行特殊宏代码可以节省大量时间。
基于选择的子框架示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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方法(OP希望避免的方法)和Range方法(这是问题的答案)。因此,当您看到第一个选择时,请不要停止阅读。
这实际上取决于你想要做什么。无论如何,一个简单的例子可能很有用。我们假设您要将活动单元格的值设置为"foo"。使用ActiveCell你会写这样的东西:
1 2 3 | Sub Macro1() ActiveCell.Value ="foo" End Sub |
如果要将其用于非活动单元格,例如"B2",则应首先选择它,如下所示:
1 2 3 4 | Sub Macro2() Range("B2").Select Macro1 End Sub |
使用范围,您可以编写一个更通用的宏,可用于设置任何您想要的单元格的值:
1 2 3 | Sub SetValue(cellAddress As String, aVal As Variant) Range(cellAddress).Value = aVal End Sub |
然后,您可以将Macro2重写为:
1 2 3 | Sub Macro2() SetCellValue"B2","foo" End Sub |
和Macro1一样:
1 2 3 | Sub Macro1() SetValue ActiveCell.Address,"foo" End Sub |
希望这有助于清理一点点。
避免
这是在以下情况下可以避免
添加新工作表并在其上复制单元格:
从(使用宏录制器生成的代码):
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
至:
1 2 3 4 5 6 7 8 9 | 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 |
如果要在工作表之间复制范围:
从:
1 2 3 4 5 6 | Sheets("Source").Select Columns("A:D").Select Selection.Copy Sheets("Target").Select Columns("A:D").Select ActiveSheet.Paste |
至:
1 | Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").Range("a1") |
使用花哨的命名范围
您可以使用
1 2 3 4 5 6 7 8 | Dim Months As Range Dim MonthlySales As Range Set Months = Range("Months") Set MonthlySales = Range("MonthlySales") Set Months =[Months] Set MonthlySales = [MonthlySales] |
上面的例子如下所示:
1 | Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").[A1] |
不是复制值,而是采用它们
通常,如果你愿意
请始终尝试引用工作表
这可能是vba最常见的错误。无论何时复制范围,有时都不会引用工作表,因此VBA会考虑ActiveWorksheet。
1 2 3 4 5 6 7 8 9 10 11 12 13 | '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 |
我真的不能使用
唯一可以证明您使用
1 2 3 | Private Sub Workbook_Open() Worksheets("Cover").Activate End Sub |
始终说明工作簿,工作表和单元格/范围。
例如:
1 2 | Thisworkbook.Worksheets("fred").cells(1,1) Workbooks("bob").Worksheets("fred").cells(1,1) |
因为最终用户总是只需单击按钮,一旦焦点移出代码想要使用的工作簿,那么事情就完全错了。
永远不要使用工作簿的索引。
1 | Workbooks(1).Worksheets("fred").cells(1,1) |
当用户运行代码时,您不知道将打开哪些其他工作簿。
这些方法相当耻辱,所以带头@Vityata和@Jeeped是为了在沙子中画一条线:
为什么不调用
基本上是因为它们主要用于通过应用程序UI处理用户输入。由于它们是用户通过UI处理对象时调用的方法,因此它们是宏录制器记录的方法,这就是为什么在大多数情况下调用它们要么脆弱要么多余:你不必选择一个对象,以便在之后使用
但是,这个定义解决了它们所要求的情况:
何时调用
基本上,当您希望最终用户在执行中发挥作用时。
如果您正在开发并希望用户选择要处理的代码的对象实例,那么
另一方面,当您可以推断用户的下一个操作并希望您的代码引导用户时,
恕我直言,
通过直接使用已经存在的对象,可以避免已经发布的
否则,
快速回答:
要避免使用
?例如,如果您想要
-
示例
valOne = Range("A1").Value
?例如,如果您想要"Sheet3"的代号,则可以设置一个等于该工作表的代码名称属性的变量。
-
示例
valTwo = Sheets("Sheet3").Codename
我希望有所帮助。如果您有任何疑问,请告诉我。
我注意到这些答案都没有提到.Offset属性。这也可用于避免在操作某些单元格时使用
这里有几个例子。
我还假设"ActiveCell"是J4。
-
这会将单元格
J6 更改为值12 - 减-2会引用J2
-
这会将
k4 中的单元格复制到L4 。 - 注意,如果不需要,偏移参数中不需要"0"(,2)
-
与前一个示例类似,减1将是
i4
- 这将清除列k中所有单元格中的值。
这并不是说它们比上述选择"更好",而只是列出替代方案。
这是一个清除单元格"A1"的内容的示例(如果选择类型是xllastcell等,则更多)。全部完成而无需选择细胞。
1 2 | Application.GoTo Reference:=Workbook(WorkbookName).Worksheets(WorksheetName).Range("A1") Range(Selection,selection(selectiontype)).clearcontents |
我希望这可以帮助别人。