592

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

我发现这篇关于范围的文章和这个关于不使用 select 的好处的例子,但我找不到任何关于如何使用的内容。

4

16 回答 16

618

如何避免选择的一些示例

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

同样,这指的是活动工作簿。除非您特别想只使用ActiveWorkbookor 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的 s 和Functions 作为 Range 变量:

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

这是可能的小尝试。

于 2012-05-23T10:23:22.667 回答
233

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

  1. 它会减慢您的代码速度。
  2. 它通常是运行时错误的主要原因。

我们如何避免它?

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/.Select。(当我遇到它们时会添加更多链接)

  1. 当您想向用户展示工作表以便用户可以看到它时。
  2. 从您被迫使用的表单控件运行时,工作宏之类的场景会返回错误.Activate
  3. 当/的常用方法不起作用时,您可能不得不求助于Text To Columns.Formula = .Formula.Select
于 2012-05-23T10:33:36.353 回答
96

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

为了避免使用 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包含的内容以及程序在做什么是非常明显的。

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

这段代码一开始就可以正常工作 - 直到您或未来的用户决定“gee wiz,我想我要在 Column 中添加一个带有年份的新列A!”,或者在月份之间放置一个费用列和销售列,或为每列添加标题。现在,您的代码已损坏。而且因为你使用了糟糕的变量名,你需要花费更多的时间来弄清楚如何修复它。

如果您一开始就使用命名范围,则MonthsandSales列可以随意移动,您的代码将继续正常工作。

于 2014-05-28T14:04:33.713 回答
50

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

每当您录制宏并重用它们时,您都会得到 .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"

这些方法有多种组合,但对于像我这样没有耐心的人来说,这将是尽快表达的一般想法。

于 2014-02-27T21:09:40.983 回答
36

“......并且我发现如果我能够使用变量而不是 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密切相关。作为工作表属性,它还有许多其他用途。.SelectActiveCell

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

于 2015-02-24T15:41:21.163 回答
32

请注意,在下文中,我将 Select 方法(OP 想要避免的方法)与 Range 方法(这是问题的答案)进行比较。所以当你看到第一个 Select 时不要停止阅读。

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

Sub 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

和 Macro1 为:

Sub Macro1()
    SetValue ActiveCell.Address, "foo"
End Sub
于 2012-05-23T06:11:28.830 回答
32

避免Select并且Activate是使您成为更好的 VBA 开发人员的举措。通常,SelectActivate录制宏时使用,因此Parent工作表或范围始终被认为是活动的。

这是您可以避免的方法SelectActivate在以下情况下:


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

来自(使用宏记录器生成的代码):

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 将始终在首先选择封面工作表的情况下打开,而不管文件关闭时哪个是 ActiveSheet。

因此,类似下面的代码是绝对可以的:

Private Sub Workbook_Open()
    Worksheets("Cover").Activate
End Sub
于 2016-03-08T10:04:12.147 回答
18

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

例如:

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

因为最终用户总是只点击按钮,一旦焦点从工作簿上移开,代码想要使用的代码就会完全出错。

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

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

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

于 2014-08-25T09:54:22.493 回答
15

这些方法相当污名化,因此为了在沙子上划清界限,请带头Vityata和 Jeeped:

调用.Activate, .Select, Selection,ActiveSomething方法/属性

基本上是因为调用它们主要是为了通过应用程序 UI 处理用户输入。由于它们是用户通过 UI 处理对象时调用的方法,它们是由宏记录器记录的方法,这就是为什么在大多数情况下调用它们既脆弱又多余:您不必选择对象,以便Selection随后执行操作。

但是,此定义解决了需要它们的情况:

何时调用.Activate, .Select, .Selection,.ActiveSomething方法/属性

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

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

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

于 2018-06-16T14:18:51.557 回答
8

恕我直言,使用.select来自像我这样的人,他们通过录制宏开始学习 VBA,然后在没有意识到这一点的情况下修改代码.select,后续selection只是一个不必要的中间人。

.select可以通过直接处理已经存在的对象来避免,正如许多已经发布的那样,这允许各种间接引用,例如以复杂的方式计算 i 和 j,然后编辑单元格(i,j)等。

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

于 2016-11-20T15:02:00.710 回答
7

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

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

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

► 例如,如果您想要该you could set a variable equal to the工作表的“Sheet3 代号”属性的代号。

  • 例子valTwo = Sheets("Sheet3").Codename
于 2016-09-08T06:36:33.133 回答
5

我注意到这些答案都没有提到.Offset 属性。这也可以用来避免Select在操作某些单元格时使用该操作,特别是在参考选定的单元格时(正如 OP 提到的那样ActiveCell)。

这里有几个例子。

我还将假设“ActiveCell”是J4

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

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

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

  • 这会将单元格复制k4L4.
  • 请注意,如果不需要,则偏移参数中不需要“0” (,2)
  • 与前面的示例类似,负 1 将是i4

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

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

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

于 2018-08-16T20:07:44.717 回答
5

如何避免复制粘贴?

让我们面对现实吧:这个在录制宏时经常出现:

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

虽然这个人唯一想要的是:

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

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

Destination_Range.Value = Source_Range.Value
于 2020-10-30T15:55:25.667 回答
3

使用.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 the 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
于 2019-10-30T09:45:23.417 回答
3

从不使用 Select 或 Activesheet 的主要原因是因为大多数人在运行宏时至少会打开另外几个工作簿(有时是几十个),并且如果他们在宏运行时单击离开工作表并单击其他一些工作簿他们打开的书,然后“Activesheet”更改,并且不合格的“Select”命令的目标工作簿也会更改。

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

我遵循一个简单的黄金法则:为 Workbook 对象和 Worksheet 对象添加名为“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”的值更改为“Hello”,而不是:

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

我们现在可以这样做:

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

如果用户正在使用多个电子表格,这不仅更加可靠并且不太可能崩溃;它也更短、更快、更容易编写。

作为一个额外的好处,如果您总是将变量命名为“wb”和“ws”,您可以将代码从一本书复制并粘贴到另一本书,并且它通常只需进行最少的更改(如果有的话)。

于 2020-03-02T13:04:58.923 回答
-4

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

Application.GoTo Reference:=Workbook(WorkbookName).Worksheets(WorksheetName).Range("A1")
Range(Selection,selection(selectiontype)).clearcontents 
于 2017-05-05T14:30:22.847 回答