如何避免在Excel VBA中使用Select

How to avoid using Select in Excel VBA

提问人:BiGXERO 提问时间:5/23/2012 最后编辑:Peter MortensenBiGXERO 更新时间:4/26/2023 访问量:387586

问:

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

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

Excel VBA

评论

23赞 Rick 5/28/2014
需要注意的是,在某些情况下,使用和/或等是完全不可避免的。这是我找到的一个例子:stackoverflow.com/questions/22796286/......SelectActiveSheet
18赞 brettdj 12/21/2014
在某些情况下 - 在 ppt 中编辑图表数据,底层 excel 文件是一个 - 需要激活或选择。
0赞 BruceWayne 11/15/2019
@brettdj - 这是最近的一个例子。要将工作簿中的所有工作表设置为相同的值,似乎是必需的。.Select / .Selection
4赞 chris neilsen 12/13/2019
来自同一 QA 的@bruce似乎并非如此

答:

36赞 Francesco Baruchelli 5/23/2012 #1

请注意,在下文中,我将 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

评论

1赞 BiGXERO 5/23/2012
感谢您如此迅速的出色反应。那么,这是否意味着如果我通常会将单元格添加到范围,命名范围并遍历它,我应该直接跳到创建数组?
0赞 Francesco Baruchelli 5/23/2012
我不确定我是否理解您的意思,但是您可以使用单个指令创建一个范围(例如Range(“B5:C14”)),甚至可以一次设置其值(如果该范围内的每个单元格都必须相同),例如Range(“B5:C14”)。值 =“abc”
667赞 chris neilsen 5/23/2012 #2

如何避免选择的一些示例

使用 'd 变量Dim

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

如果您确实想使用 ,为了清楚起见,最好是明确的。但要小心,因为有些方法会更改活动工作表。ActiveSheetWorksheet

Set rng = ActiveSheet.Range("A1")

同样,这是指活动工作簿。除非您特别想只使用 or ,否则最好也对变量进行调暗。ActiveWorkbookThisWorkbookWorkbook

Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")

如果您确实想使用 ,为了清楚起见,最好是明确的。但要小心,因为许多方法会改变活动书。ActiveWorkbookWorkBook

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

将范围作为 Range 变量传递给 s 和 s: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

这是一个小小的品尝者,可以尝试什么。

评论

12赞 MikeD 1/13/2015
除了这个绝妙的答案之外,为了使用一个范围,只要你知道左上角,你就不需要知道它的实际大小......例如 即使 RNG1 设置为 ONLY,也会起作用。rng1(12, 12)[A1:A10]
4赞 Logan Reed 7/16/2016
@chrisneilsen Chris,我相信您还可以在速记单元格引用表示法之前使用工作表前缀,以免您像这样键入:或.RangeActiveSheet.[a1:a4]ws.[b6]
7赞 ashleedawg 4/7/2018
@AndrewWillems......或在这篇文章中 48 次,但谁在计算。☺ ...但说真的,在处理包含对象的变量时,很容易忘记这一点。在为变量分配对象之前,变量不需要。例如,没问题,但会生成错误 438。但是,仅仅混淆/澄清不会产生错误。为什么?...因为它将对象的值分配给变量,而不是对对象本身的引用(因为它等效于variantSetDim x: x = 1Dim x: x = Sheets("Sheet1")Dim x: x = Range("A1")Dim x: x = Range("A1").Value)
3赞 chris neilsen 12/18/2018
@user3932000这将是一个有趣的问题。我相信有办法处理它..你已经在 So 上呆了足够长的时间,知道劫持旧 Q 上的评论线程不是要走的路
3赞 LWChris 4/26/2021
一个小补充:我发现不将变量命名为“wb1”和“wb2”非常有用,但如果我正在复制,则命名为“wbTarget”和“wbSource”。有助于跟踪哪个参数在哪里。
252赞 Siddharth Rout 5/23/2012 #3

两个主要原因。选择应避免使用 Activate、SelectionActivecellActivesheetActiveworkbook

  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

评论

19赞 user3032689 2/2/2016
这是一个很好的答案,但我在这个话题上缺少的是当我们真正需要激活时。每个人都说它不好,但没有人解释任何使用它有意义的情况。例如,我正在使用 2 个工作簿,如果不先激活它,就无法在其中一个工作簿上启动宏。你能详细说明一下吗?此外,例如,如果我在将范围从一个工作表复制到另一个工作表时没有激活工作表,那么当我执行程序时,它似乎无论如何都会隐式地激活相应的工作表。
1赞 Nick 11/25/2016
我发现,如果您需要在工作表上粘贴或过滤数据,有时可能需要先激活工作表。我会说最好避免尽可能多地激活,但在某些情况下您需要这样做。因此,请根据上面的答案继续激活和选择到绝对最低限度。
7赞 robotik 6/20/2017
我认为重点不是完全避免使用它们,而是尽可能多地使用它们。如果要保存工作簿,以便在有人打开它时选择某个工作表中的某个单元格,则必须选择该工作表和单元格。复制/粘贴是一个不好的例子,至少在值的情况下,可以通过诸如Sheets(2).[C10:D12].Value = Sheets(1).[A1:B3].Value
3赞 phrebh 1/9/2018
@Nick 您无需激活工作表即可粘贴到工作表或过滤工作表。在粘贴或筛选命令中使用工作表对象。当您通过练习学习 Excel 对象模型时,它会变得更容易。我相信我唯一一次使用.“激活”是指我创建一个新工作表,但我希望在代码完成后显示原始工作表。
3赞 GMalc 3/1/2019
@phrebh 您无需使用即可移动到原始工作表,只需使用.ActivateApplication.Goto
55赞 MattB 2/28/2014 #4

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

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

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

109赞 Rick 5/28/2014 #5

我要强调一点,我要补充一下之前给出的所有优秀答案:

为了避免使用 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

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

如果您一开始就使用了命名范围,则可以随心所欲地移动 and 列,并且您的代码将继续正常工作。MonthsSales

评论

7赞 brettdj 2/27/2015
关于命名范围是好是坏的争论仍在继续 - 我坚定地站在反对阵营。根据我的经验,它们会增加错误(对于不需要代码的标准用户)。
6赞 brettdj 2/28/2015
一个参考 范围名称是否妨碍了新手调试性能?
12赞 DeanOC 3/25/2015
我同意你的发展理念;但是我认为这篇论文是无稽之谈。它谈到了范围名称如何使调试电子表格的新手感到困惑,但任何使用新手查看复杂电子表格的人都会得到他们应得的!我曾经在一家审查财务电子表格的公司工作,我可以告诉你,这不是你给新手的那种工作。
9赞 Excel Hero 8/21/2015
没有有意义的辩论。任何反对定义名称的人都没有花时间充分理解它们的后果。命名公式可能是所有 Excel 中最深刻和最有用的构造。
12赞 Marcus Mangelsdorf 11/3/2017
@brettdj:你的引文是正确的,但你忘了提到它后面跟着六个“除了......”短语。其中之一是:“除了在宏编码中作为单元格引用的替代品外,在构造宏时始终使用 Excel 名称作为单元格引用的替代品。这是为了避免因插入额外的行或列而产生的错误,从而使宏编码不再指向预期的源数据。
20赞 user1644564 8/25/2014 #6

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

例如:

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

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

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

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

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

评论

7赞 Rick 11/23/2014
你知道,工作表的名称也可以改变。请改用代号。
0赞 Geoff Griswald 3/2/2020
当然,工作表名称可以更改。但我不同意你应该把你的代码过于复杂化,以试图缓解这种情况。如果用户更改了工作表的名称,并且他们的宏停止工作,则该宏在他们身上。我通常只是假设工作表名称是相同的。对于特别关键的宏,在启动宏之前,我会运行一些飞行前检查,它只是检查以确保它期望找到的所有工作表确实存在,如果缺少任何工作表,它会通知用户哪一个。
40赞 user4039065 2/24/2015 #7

"...并且我发现,如果我能够使用变量而不是 Select 函数,我的代码将更具可重用性。

虽然我想不出只有少数孤立的情况比直接单元格引用更好,但我会挺身而出,并指出不应该出于应该避免的相同原因将其抛弃。.SelectSelection.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

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

评论

14赞 L42 5/20/2015
Selection可以是工作表中的任何内容,因此在将对象分配给变量之前,最好先测试对象的类型,因为您显式将其声明为 .Range
1赞 Geoff Griswald 1/23/2021
我更进一步,创建了一个 excel 加载项,该加载项适用于用户选择的单元格,并在右键单击上下文菜单中添加了一个按钮,该按钮在选择单元格以触发它时出现。我的用户绝对喜欢它。对他们来说,就好像我的宏是 Excel 的内置功能一样,他们不知道有什么不同。
39赞 Vityata 3/8/2016 #8

避免 和 是使您成为更好的 VBA 开发人员的举动。通常,在录制宏时使用,因此工作表或范围始终被视为活动工作表或范围。SelectActivateSelectActivateParent

在以下情况下,您可以避免选择激活


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

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

  • 一个很好的例子是,当你想要确保出于视觉原因选择特定的工作表时,你可以合理地使用和。例如,您的 Excel 将始终在首先选择封面工作表的情况下打开,而忽略文件关闭时哪个是 ActiveSheet。.Activate.Select

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

Private Sub Workbook_Open()
    Worksheets("Cover").Activate
End Sub

评论

1赞 Geoff Griswald 2/21/2020
可以使用 Application.Goto 而不是 Worksheets.Activate。风险稍小一些。
1赞 T.M. 3/11/2020
延迟回复仅供参考 - 一个不错的且有点出乎意料的例子 - 以及我的工作 - 可以在 如何将相同的信息写入所有工作表 - @Vityata :).Select
1赞 Vityata 3/11/2020
@T.M. - 确实是一个有趣的例子,可能会为 100+ 工作表节省一些毫秒,但如果我在某处看到它,我可能会劝阻它。无论如何,那里的选择不是明确编写的,而是 的结果,所以这介于两者之间(至少在我关于 VBA 分类法的想法中).FillAcrossSheets
11赞 FinPro.Online 9/8/2016 #9

若要避免使用该方法,可以设置一个与所需属性相等的变量。.Select

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

  • valOne = Range("A1").Value

例如,如果您想要“Sheet3”的代号,则可以设置一个与该工作表的属性相等的变量。Codename

  • valTwo = Sheets("Sheet3").Codename
11赞 Eleshar 11/20/2016 #10

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

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

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

评论

2赞 vacip 11/22/2016
虽然你是对的,但 select 至少有一件事隐含着错误:它很慢。与宏中发生的所有其他事情相比,确实非常缓慢。
18赞 LFB 6/16/2018 #11

这些方法相当污名化,因此为了在沙子上画一条线而以 Vityata 和 Jeeped 为主导:

调用 、 、 方法/属性.Activate.SelectSelectionActiveSomething

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

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

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

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

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

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

评论

0赞 Peter Mortensen 9/29/2020
“吉普车”指的是什么答案?该用户名当前没有答案或评论(用户名可能随时更改) - 包括已删除的答案。候选项是 user4039065 的答案(现已删除用户)。
6赞 pgSystemTester 8/17/2018 #12

我注意到这些答案都没有提到 .Offset 属性。这也可用于避免在操作某些单元格时使用该操作,尤其是在参考所选单元格时(如 OP 中提到的 )。SelectActiveCell

以下是几个示例:

我还假设是 J4ActiveCell

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

  • 这会将单元格的值(即 J6)更改为 12activecell
  • 负 -2 会将值 12 放在 J2 中两行以上

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

  • 这会将单元格向右 (k4) 的一列复制到从 (L4) 到单元格的两列。activecell
  • 请注意,offset 参数中可能会省略0
    • 所以:和ActiveCell.Offset(,2)ActiveCell.Offset(0,2)
  • 与前面的示例类似,-1 将是左边的一列 (i4)

这并不意味着这些选项比上述选项更好,但绝对比使用 .请注意,应避免在工作表中使用 EXCEL FUNCTION Offset,因为它是一个易失性函数。select

5赞 barneyos 10/30/2019 #13

使用 .父功能,此示例显示了如何仅设置一个 myRng 引用,从而实现对整个环境的动态访问,而无需任何 .选择。激活。Activecell,.ActiveWorkbook、.ActiveSheet 等。(没有任何通用的.子功能。

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

评论

3赞 Geoff Griswald 3/2/2020
非常好,但不确定这与 OP 问题有什么关系。您根本不需要“父级”即可在不使用 Select 或 ActiveSheet 的情况下在 VBA 中工作
6赞 Geoff Griswald 3/2/2020 #14

从不使用“选择”或“活动表”的主要原因是因为大多数人在运行宏时至少会打开另外几个工作簿(有时是几十个),如果他们在运行宏时单击离开工作表并单击他们打开的其他书籍,则“活动表”会更改,并且非限定的“选择”命令的目标工作簿也会更改。

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

我有一条简单的黄金法则:为 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 代码的工作簿。一个非常有用的快捷方式,用于设置工作簿变量。

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

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

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

我们现在可以这样做:

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

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

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

评论

1赞 BigBen 3/2/2020
不是我的反对票,但我不确定这是否为现有答案中已经提出的内容增加了任何新内容。
1赞 Geoff Griswald 3/4/2020
是的,我的答案有点多余,但其他答案太长了,包含太多多余的东西,没有人提到使用 ThisWorkbook 预先设置工作表变量。如果有人在我第一次接触 VBA 时向我展示这一点,我会发现非常有帮助。其他人提到了使用 Worksheet 变量,但并没有很好地解释原因,也没有提供使用和不使用工作表和工作簿变量的代码示例。
0赞 BigBen 3/4/2020
但公认的答案肯定是讨论......我不确定你的评论是否准确。ThisWorkbook
0赞 Geoff Griswald 3/5/2020
确实如此,你没有错。但不是在使用它来设置工作簿变量并使用该工作簿变量的上下文中,或者使用该工作簿变量来设置工作表变量,正如我所建议的那样。我的答案比公认的答案更短、更简单、更容易为初学者所接受。
10赞 Dominique 10/30/2020 #15

如何避免复制粘贴?

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

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

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

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

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

Destination_Range.Value = Source_Range.Value

评论

2赞 T.M. 10/31/2020
认为这与@Vityata的好答案相对应,“不是复制值,而是获取它们”一节:.当然,上面的一些答案并不面对OP的核心问题:-)Range("B1:B6").Value = Range("A1:A6").Value
0赞 chris neilsen 1/21/2023
这在章节标题中公认的答案中也得到了解决:如果要遍历一系列单元格,通常最好(更快)先将范围值复制到变体数组中,然后遍历该数组: