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

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


当前回答

在我看来,.select的使用来自于像我这样的人,他们通过记录宏然后修改代码开始学习VBA,而没有意识到.select和后续的选择只是一个不必要的中间人。

.select可以避免,就像许多人已经发布的那样,通过直接使用已经存在的对象,这允许各种间接引用,如以复杂的方式计算I和j,然后编辑cell(I,j)等。

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

其他回答

“…我发现,如果我能够使用变量而不是选择函数,我的代码将更加可重用。”

虽然我想不出更多孤立的情况,在这些情况下. select将是比直接引用单元格更好的选择,但我要为Selection辩护,并指出它不应该因为应该避免. select的原因而被抛弃。

There are times when having short, time-saving macro sub routines assigned to hot-key combinations available with the tap of a couple of keys saves a lot of time. Being able to select a group of cells to enact the operational code on works wonders when dealing with pocketed data that does not conform to a worksheet-wide data format. Much in the same way that you might select a group of cells and apply a format change, selecting a group of cells to run special macro code against can be a major time saver.

基于选择的子框架示例:

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程序员可能推断的任何误解。)

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

这真的取决于你想要做什么。总之,举个简单的例子会很有用。让我们假设您想要将活动单元格的值设置为“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

一些如何避免选择的例子

使用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]

以上所有示例都引用活动工作表上的单元格。除非你特别想只使用活动表,否则最好将工作表变量也调暗:

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,为了清晰起见,最好是显式的。但是要小心,因为有些工作表方法会更改活动表。

Set rng = ActiveSheet.Range("A1")

同样,这是指活动工作簿。除非你特别想只使用ActiveWorkbook或ThisWorkbook,否则最好也使用Dim 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 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

这是对可能性的小小尝试。

这些方法都是被污名化的,所以以Vityata和jeomed为首为了划清界限:

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

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

然而,这个定义适用于需要它们的情况:

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

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

如果您正在开发并希望用户为您的代码选择要处理的对象实例,那么. selection或. activeobject是合适的。

On the other hand, .Select and .Activate are of use when you can infer the user's next action and you want your code to guide the user, possibly saving him/her some time and mouse clicks. For example, if your code just created a brand new instance of a chart or updated one, the user might want to check it out, and you could call .Activate on it or its sheet to save the user the time searching for it; or if you know the user will need to update some range values, you can programmatically select that range.

两个主要原因,为什么.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