如何避免在Excel VBA中使用Select

时间:2012-05-23 05:57:58

标签: excel vba

我听说过很多关于在Excel VBA中使用.Select的可理解的憎恶,但我不确定如何避免使用它。如果我能够使用变量而不是Select函数,我发现我的代码将更易于重用。但是,如果不使用ActiveCell,我不确定如何引用内容(例如Select等)。

我找到this article on rangesthis example on the benefits of not using select但在 上找不到任何内容?

16 个答案:

答案 0 :(得分:487)

如何避免选择

的一些示例

使用Dim'd变量

Dim rng as Range

Set变量到所需范围。有很多方法可以引用单细胞范围

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")

同样,这是指活动工作簿。除非您特别希望仅使用ActiveWorkbookThisWorkbook,否则最好还要对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

将范围传递给SubFunction作为范围变量

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

您还应该将方法(例如FindCopy)应用于变量

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

这是一个小小的品尝者。

答案 1 :(得分:189)

.Select / .Activate / Selection / Activecell / Activesheet / Activeworkbook等的两个主要原因......要避免

  1. 这会降低您的代码速度。
  2. 这通常是运行时错误的主要原因。
  3. 我们如何避免它?

    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
    

答案 2 :(得分:76)

我将在上面给出的所有优秀答案中添加一个小点:

您可以做的最大的事情是避免使用Select,尽可能 ,在VBA代码中使用命名范围(结合有意义的变量名称) 。上面提到了这一点,但有点掩饰;但是,它值得特别关注。

虽然我确信我可以考虑更多,但还有一些其他理由可以自由使用命名范围。

命名范围使您的代码更易于阅读和理解。

示例:

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

很明显,命名范围MonthsMonthlySales包含什么,以及程序正在做什么。

为什么这很重要?部分是因为其他人更容易理解它,但即使你是唯一一个会看到或使用你的代码的人,你仍然应该使用命名范围和好的变量名,因为 你将忘记< / em> 一年后您的意图是什么, 您将浪费 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

此代码最初会正常工作 - 直到您或将来的用户决定使用,我想我将在列{{1}中添加一年的新列}!&#34;,或在月份和销售列之间放置费用列,或者为每列添加标题。现在,您的代码已损坏。而且因为你使用了可怕的变量名,所以你需要花费更多的时间来弄清楚它应该如何修复它。

如果您开始使用命名范围,AMonths列可以随心所欲地移动,您的代码将继续正常工作。

答案 3 :(得分:43)

我会给出简短的答案,因为其他人都给了很长的答案。

每当你录制宏并重用它们时,你都会得到.select和.activate。当你选择一个单元格或工作表时,它就会激活它。从那时起,无论何时使用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"

这些方法有各种各样的组合,但对于像我这样的不耐烦的人,这将是尽快表达的一般观点。

答案 4 :(得分:30)

  

“......如果我能够使用变量而不是Select函数,我发现我的代码会更加可重用。”

虽然我不能想到只有少数情况.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

要处理的实际代码可以是从单行到多个模块的任何内容。我已经使用这种方法在包含外部工作簿文件名的不规则选择的单元格上启动长时间运行的例程。

简而言之,由于与Selection.Select的密切关联,请不要弃置ActiveCell。作为工作表属性,它有许多其他用途。

(是的,我知道这个问题是关于.Select,而不是Selection,但我想删除新手VBA编码员可能推断出的任何误解。)

答案 5 :(得分:29)

请注意,在下面我将比较Select方法(OP希望避免的方法)和Range方法(这是问题的答案)。因此,当您看到第一个选择时,请不要停止阅读。

这实际上取决于你想要做什么。无论如何,一个简单的例子可能很有用。我们假设您要将活动单元格的值设置为“foo”。使用ActiveCell你会写这样的东西:

Sub Macro1()
    ActiveCell.Value = "foo"
End Sub

如果你想将它用于非活动的单元格,例如“B2”,你应该先选择它,如下所示:

Sub Macro2()
    Range("B2").Select
    Macro1
End Sub

使用范围,你可以编写一个更通用的宏,可以用来设置你想要的任何单元格的值:

Sub SetValue(cellAddress As String, aVal As Variant)
    Range(cellAddress).Value = aVal
End Sub

然后您可以将Macro2重写为:

Sub Macro2()
    SetCellValue "B2", "foo"
End Sub

和Macro1一样:

Sub Macro1()
    SetValue ActiveCell.Address, "foo"
End Sub

希望这有助于清理一些事情。

答案 6 :(得分:21)

避免使用SelectActivate这一举措会让您的VBA开发人员变得更好。通常,在记录宏时会使用SelectActivate,因此Parent工作表或范围始终被视为活动的。

在以下情况下,您可以避免SelectActivate

添加新工作表并在其上复制单元格:

From(使用宏录制器生成的代码):

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会考虑ActiveWorksheet。

'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

答案 7 :(得分:15)

始终说明工作簿,工作表和单元格/范围。

例如:

Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)

因为最终用户总是只需单击按钮,一旦焦点移出工作簿,代码就会想要处理,那么事情就完全错了。

永远不要使用工作簿的索引。

Workbooks(1).Worksheets("fred").cells(1,1)

当用户运行代码时,您不知道将打开哪些其他工作簿。

答案 8 :(得分:8)

这些方法相当耻辱,所以带头@Vityata和@Jeeped是为了在沙子中划一条线:

为什么不拨打.Activate.SelectSelectionActiveSomething方法/属性

基本上是因为他们主要通过应用程序UI来处理用户输入。由于它们是用户通过UI处理对象时调用的方法,因此它们是宏录制器记录的对象,这就是为什么在大多数情况下调用它们要么脆弱要么冗余:你不必选择一个对象,以便在事后用Selection执行一个动作。

然而,这个定义解决了他们被要求的情况:

何时致电.Activate.Select.Selection.ActiveSomething方法/属性

基本上,当您希望最终用户在执行中发挥作用时。

如果您正在开发并期望用户选择要处理的代码的对象实例,那么.Selection.ActiveObject是合适的。

另一方面,当您可以推断用户的下一个操作并希望您的代码能够引导用户时,.Select.Activate是有用的,可能会节省他一些时间和鼠标点击。例如,如果您的代码刚刚创建了一个全新的图表实例或更新过的图表,则用户可能需要将其检出,您可以在其上或其工作表上调用.Activate以节省用户搜索时间它;或者如果您知道用户需要更新某些范围值,您可以以编程方式选择该范围。

答案 9 :(得分:5)

恕我直言使用.select来自于那些喜欢我的人开始通过录制宏开始学习VBA然后修改代码而没有意识到.select和后续selection只是一个不必要的中间人。

通过直接使用现有对象,可以避免

.select已经发布,这可以允许各种间接引用,例如以复杂的方式计算i和j,然后编辑单元格(i,j),等

否则,.select本身没有任何隐含的错误,您可以轻松找到此用途,例如我有一个电子表格,我用日期填充,激活宏,用它做一些魔术,并以可接受的格式将其导出到一个单独的工作表上,然而,这需要一些最终的手动(不可预测)输入到相邻的单元格。所以.select的时刻到来为我节省了额外的鼠标移动和点击。

答案 10 :(得分:4)

快速回答:

为避免使用.Select方法,您可以设置一个等于所需属性的变量。

►例如,如果您想要Cell A1中的值,则可以将变量设置为等于该单元格的value属性。

  • 示例valOne = Range("A1").Value

►例如,如果您想要“#3; Sheet3”的代号,则可以设置一个等于该工作表的代号属性的变量。

  • 示例valTwo = Sheets("Sheet3").Codename

我希望有所帮助。如果您有任何问题,请告诉我。

答案 11 :(得分:2)

我注意到这些答案都没有提到.Offset Property。这也可以用来避免在处理某些单元格时使用Select动作,尤其是在引用所选单元格时(如OP在ActiveCell中提到的那样)。

这里有几个例子。

我还将假定“ ActiveCell”为 J4

ActiveCell.Offset(2, 0).Value = 12

  • 这会将单元格J6的值更改为12
  • 减-2会引用J2

ActiveCell.Offset(0,1).Copy ActiveCell.Offset(,2)

  • 这会将k4中的单元格复制到L4
  • 请注意,如果不需要,则offset参数中不需要“ 0”(,2)
  • 类似于前面的示例,负1将为i4

ActiveCell.Offset(, -1).EntireColumn.ClearContents

  • 这将清除k列中所有单元格中的值。

这些并不是说它们比上述选项“更好”,而只是列出了替代方案。

答案 12 :(得分:1)

如何避免复制粘贴?

面对现实:录制宏时,这个出现很多:

Range("X1").Select
Selection.Copy
Range("Y9).Select
Selection.Paste

该人唯一想要的是:

Range("Y9").Value = Range("X1").Value

因此,我建议不要使用以下简单方法:在VBA宏中使用复制粘贴:

Destination_Range.Value = Source_Range.Value

答案 13 :(得分:0)

这是一个清除单元格“A1”内容的示例(如果选择类型是xllastcell等,则更多)。全部完成而无需选择细胞。

Application.GoTo Reference:=Workbook(WorkbookName).Worksheets(WorksheetName).Range("A1")
Range(Selection,selection(selectiontype)).clearcontents 

我希望这有助于某人。

答案 14 :(得分:0)

使用.Parent功能。本示例说明了如何仅设置一个myRng引用就可以在不使用.Select,.Activate,.Activecell,.ActiveWorkbook,.ActiveSheet等的情况下动态访问整个环境。 (没有宽泛的.Child功能)

Sub ShowParents()
    Dim myRng As Range
    Set myRng = ActiveCell
    Debug.Print myRng.Address                    ' an address of the selected cell
    Debug.Print myRng.Parent.name                ' the name of sheet, where MyRng is in
    Debug.Print myRng.Parent.Parent.name         ' the name of workbook, where MyRng is in
    Debug.Print myRng.Parent.Parent.Parent.name  ' the name of application, where MyRng is in

    ' You may use this feature to set reference to these objects
    Dim mySh    As Worksheet
    Dim myWbk   As Workbook
    Dim myApp   As Application

    Set mySh = myRng.Parent
    Set myWbk = myRng.Parent.Parent
    Set myApp = myRng.Parent.Parent.Parent
    Debug.Print mySh.name, mySh.Cells(10, 1).Value
    Debug.Print myWbk.name, myWbk.Sheets.Count
    Debug.Print myApp.name, myApp.Workbooks.Count

    ' You may use dynamically addressing
    With myRng
        .Copy

       ' pastes in D1 on sheet 2 in the same workbook, where copied cell is
        .Parent.Parent.Sheets(2).Range("D1").PasteSpecial xlValues
    ' or myWbk.Sheets(2).Range("D1").PasteSpecial xlValues

       ' we may dynamically call active application too
        .Parent.Parent.Parent.CutCopyMode = False
    ' or myApp.CutCopyMode = False
    End With
End Sub

答案 15 :(得分:0)

从不使用Select或Activesheet的主要原因是,大多数人在运行宏时会打开至少几本工作簿(有时几十本),并且在宏运行时单击远离工作表并单击在其他已打开的书上,“ Activesheet”也随之更改,并且不合格的“ Select”命令的目标工作簿也随之更改。

充其量,您的宏将崩溃,最坏的情况是您可能最终在错误的工作簿中写入值或更改单元格而无法“撤消”它们。

我遵循一个简单的黄金法则:为工作簿对象和工作表对象添加名为“ wb”和“ ws”的变量,并始终使用它们引用我的宏书。如果我需要参考多于一本书或多于一页纸,则添加更多变量。

例如,

Dim wb as Workbook
Dim ws as Worksheet
Set wb = ThisWorkBook
Set ws = wb.sheets("Output")

“ Set wb = ThisWorkbook”命令绝对是关键。 “ ThisWorkbook”在Excel中是一个特殊的值,它表示您的VBA代码当前从其运行的工作簿。一个非常有用的快捷方式,用于设置Workbook变量。

在Sub的顶部完成该操作之后,使用它们再简单不过了,只要在要使用“选择”的地方使用它们即可:

因此要将“输出”中单元格“ A1”的值更改为“你好”,而不是:

Sheets("Output").Activate
ActiveSheet.Range("A1").Select
Selection.Value = "Hello"

我们现在可以这样做:

ws.Range("A1").Value = "Hello"

如果用户使用多个电子表格,这不仅更可靠,而且更不会崩溃;它也更短,更快,更容易编写。

作为一项额外的奖励,如果您始终将变量命名为“ wb”和“ ws”,则可以将代码从一本书复制并粘贴到另一本书,并且通常只需很少的更改即可工作,如果有的话。