Как избежать использования Select в Excel VBA

May 23 2012

Я много слышал об понятном отвращении к использованию .SelectVBA в Excel, но не знаю, как этого избежать. Я обнаружил, что мой код можно было бы использовать повторно, если бы я мог использовать переменные вместо Selectфункций. Однако я не уверен, как относиться к вещам (например ActiveCell, и т. Д.), Если они не используются Select.

Я нашел эту статью о диапазонах и этот пример о преимуществах отказа от использования select , но я не могу найти ничего о том, как это сделать .

Ответы

578 chrisneilsen May 23 2012 at 17:23

Некоторые примеры того, как избежать выбора

Используйте 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")

Опять же, это относится к активной книге . Если вы специально не хотите работать только с ActiveWorkbookили 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

Передайте диапазоны вашим Subs и 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

Вы также должны применять методы (например, 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

Это небольшой пример того, что возможно.

217 SiddharthRout May 23 2012 at 17:33

Две основные причины .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
89 RicksupportsMonica May 28 2014 at 21:04

Я добавлю один небольшой акцент ко всем превосходным ответам, данным ранее:

Вероятно, самое важное, что вы можете сделать, чтобы избежать использования 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

Это довольно очевидно , что названные диапазоны Monthsи MonthlySalesсодержат, и то , что делает процедуру.

Почему это важно? Частично потому, что другим людям это легче понять, но даже если вы единственный человек, который когда-либо увидит или использует ваш код, вам все равно следует использовать именованные диапазоны и хорошие имена переменных, потому что вы забудете, что собирались с ним делать. год спустя, и вы потратите 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!» Или поместить столбец расходов между месяцами и столбцы продаж или добавьте заголовок в каждый столбец. Теперь ваш код не работает. А поскольку вы использовали ужасные имена переменных, вам потребуется гораздо больше времени, чтобы выяснить, как это исправить, чем следовало бы.

Если вы использовали именованные диапазоны , чтобы начать с того , что Monthsи Salesстолбцы могут быть перемещены все , что вам нравится, и ваш код будет продолжать работать нормально.

48 MattB Feb 28 2014 at 04:09

Я собираюсь дать короткий ответ, поскольку все остальные дали длинный.

Вы получите .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"

Существуют различные комбинации этих методов, но это была бы общая идея, выраженная как можно короче для нетерпеливых людей вроде меня.

34 Noname Feb 24 2015 at 22:41

«... и я обнаружил, что мой код можно было бы использовать повторно, если бы я мог использовать переменные вместо функций выбора».

Хотя я не могу придумать ничего, кроме изолированной горстки ситуаций, где .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.)

30 Vityata Mar 08 2016 at 17:04

Избегайте Selectи Activate- это шаг, который сделает вас немного лучше разработчика VBA. В общем, Selectи Activateиспользуются , когда макрос записывается, таким образом, Parentрабочий лист или диапазон всегда считается активным.

Этого можно избежать Selectи Activateв следующих случаях:


Добавление нового рабочего листа и копирование на нем ячейки:

От (код, созданный с помощью средства записи макросов):

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 . Всякий раз, когда вы копируете диапазоны, иногда на лист не ссылаются, и поэтому 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
  • Еще один хороший пример - когда вам нужно экспортировать все листы в один файл PDF, как упоминалось в этом случае - как избежать операторов select / active в VBA в этом примере?

  • Когда команда работает только с ActiveWindowкак ActiveWindow.Zoom или ActiveWindow.FreezePanes

30 FrancescoBaruchelli May 23 2012 at 13:11

Обратите внимание, что ниже я сравниваю подход Select (тот, которого OP хочет избежать) с подходом Range (и это ответ на вопрос). Так что не прекращайте читать, когда увидите первый Select.

Это действительно зависит от того, что вы пытаетесь сделать. В любом случае простой пример может быть полезен. Предположим, вы хотите установить значение активной ячейки на «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
18 user1644564 Aug 25 2014 at 16:54

Всегда указывайте книгу, лист и ячейку / диапазон.

Например:

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

Поскольку конечные пользователи всегда будут просто нажимать кнопки, и как только фокус перемещается с рабочей книги, с которой хочет работать код, все идет совершенно не так.

И никогда не используйте индекс книги.

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

Вы не знаете, какие еще книги будут открыты, когда пользователь запустит ваш код.

11 LFB Jun 16 2018 at 21:18

Эти методы довольно стигматизированы, поэтому взять на себя инициативу Vityata и Jeeped, чтобы провести черту на песке:

Вызов .Activate, .Select, Selection, ActiveSomethingметоды / свойства

В основном потому, что они вызываются в первую очередь для обработки пользовательского ввода через пользовательский интерфейс приложения. Поскольку это методы, вызываемые, когда пользователь обрабатывает объекты через пользовательский интерфейс, они записываются макро-регистратором, и поэтому их вызов либо хрупкий, либо избыточный в большинстве ситуаций: вам не нужно выбирать объект, чтобы Selectionсразу после этого выполнить действие .

Однако это определение разрешает ситуации, в которых они требуются:

Когда звонить .Activate, .Select, .Selection, .ActiveSomethingметоды / свойства

В основном, когда вы ожидаете, что конечный пользователь будет играть роль в выполнении.

Если вы разрабатываете и ожидаете, что пользователь выберет экземпляры объекта для обработки вашего кода, тогда .Selectionили .ActiveObjectявляются подходящими.

С другой стороны, .Selectи .Activateиспользуются, когда вы можете сделать вывод о следующем действии пользователя и хотите, чтобы ваш код направлял пользователя, возможно, сэкономив ему / ей время и количество щелчков мыши. Например, если ваш код только что создал новый экземпляр диаграммы или обновил его, пользователь может захотеть проверить его, и вы можете вызвать .Activateего или его лист, чтобы сэкономить время пользователя на его поиск; или если вы знаете, что пользователю потребуется обновить некоторые значения диапазона, вы можете программно выбрать этот диапазон.

6 Eleshar Nov 20 2016 at 22:02

ИМХО использование .selectисходит от людей, которые, как я, начали изучать VBA по необходимости, записывая макросы, а затем изменяя код, не осознавая этого, .selectа затем selection- просто ненужные посредники.

.select можно избежать, как многие уже писали, напрямую работая с уже существующими объектами, что позволяет использовать различные косвенные ссылки, такие как вычисление i и j сложным способом, а затем редактирование ячейки (i, j) и т. д.

В противном случае нет ничего неявно неправильного с .selectсамим собой, и вы можете легко найти применение для этого, например, у меня есть электронная таблица, которую я заполняю датой, активирую макрос, который делает с ним некоторую магию, и экспортирует его в приемлемом формате на отдельном листе, который однако требует некоторых заключительных ручных (непредсказуемых) вводов в соседнюю ячейку. Вот и настал момент, когда .selectэто избавляет меня от лишних движений мыши и щелчков.

4 FinPro.Online Sep 08 2016 at 13:36

Чтобы избежать использования этого .Selectметода, вы можете установить переменную, равную желаемому свойству.

► Например, если вы хотите ввести значение, Cell A1вы можете установить переменную, равную свойству value этой ячейки.

  • пример valOne = Range("A1").Value

► Например, если вам нужно кодовое you could set a variable equal to theимя свойства Sheet3 Codename для этого рабочего листа.

  • пример valTwo = Sheets("Sheet3").Codename
3 PGSystemTester Aug 17 2018 at 03:07

Я заметил, что ни в одном из этих ответов не упоминается свойство .Offset . Это также можно использовать, чтобы избежать использования 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.
  • Обратите внимание, что «0» не требуется в параметре смещения, если он не нужен (, 2)
  • Как и в предыдущем примере, минус 1 будет i4

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

  • Это очистит значения во всех ячейках столбца k.

Это не значит, что они «лучше», чем вышеперечисленные варианты, а просто перечисление альтернатив.

1 Dominique Oct 30 2020 at 22:55

Как избежать копипаста?

Посмотрим правде в глаза: этот часто появляется при записи макросов:

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

А человек хочет только:

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

Поэтому вместо использования копипаста в макросах VBA я бы посоветовал следующий простой подход:

Destination_Range.Value = Source_Range.Value
barneyos Oct 30 2019 at 16:45

Работая с функцией .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
GeoffGriswald Mar 02 2020 at 20:04

Основная причина никогда не использовать Select или Activesheet заключается в том, что у большинства людей будет открыта по крайней мере еще пара книг (иногда десятки), когда они запускают ваш макрос, и если они щелкают вдали от вашего листа во время выполнения вашего макроса и нажимают какой-либо другой книга, которую они открыли, то "Активная таблица" изменяется, и целевая книга для неквалифицированной команды "Выбрать" также изменяется.

В лучшем случае ваш макрос выйдет из строя, в худшем вы можете в конечном итоге записать значения или изменить ячейки в неправильной книге, не имея возможности «отменить» их.

У меня есть простое золотое правило, которому я следую: добавляйте переменные с именами «wb» и «ws» для объекта Workbook и объекта Worksheet и всегда используйте их для ссылки на мою книгу макросов. Если мне нужно сослаться на несколько книг или несколько листов, я добавляю больше переменных.

Например,

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

Команда "Set wb = ThisWorkbook" абсолютно важна. ThisWorkbook - это особое значение в Excel, и оно означает книгу, из которой в настоящее время выполняется ваш код VBA . Очень полезный ярлык для установки переменной Workbook с помощью.

После того, как вы сделали это в верхней части подпрограммы, их использование не может быть проще, просто используйте их везде, где вы использовали бы «Выбор»:

Итак, чтобы изменить значение ячейки «A1» в «Output» на «Hello» вместо:

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

Теперь мы можем сделать это:

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

Что не только намного надежнее и с меньшей вероятностью приведет к сбою, если пользователь работает с несколькими электронными таблицами; это также намного короче, быстрее и легче писать.

В качестве дополнительного бонуса, если вы всегда называете свои переменные «wb» и «ws», вы можете копировать и вставлять код из одной книги в другую, и это обычно будет работать с минимальными необходимыми изменениями, если таковые имеются.