it-roy-ru.com

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

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

Я нашел эту статью о диапазонах и этот пример о преимуществах неиспользования select но не могу найти что-нибудь о how?

454
BiGXERO

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

Используйте 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, лучше также 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 и Function в качестве переменных 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

Это небольшой дегустатор для того, что возможно.

463
chris neilsen

Две основные причины, по которым следует избегать .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
180
Siddharth Rout

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

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

Сначала этот код будет работать нормально - пока вы или будущий пользователь не примете решение "gee wiz, я думаю, что я собираюсь добавить новый столбец с годом в столбце A!" Или поставить столбец расходов между месяцами и столбцы продаж, или добавьте заголовок к каждому столбцу. Теперь ваш код не работает. А поскольку вы использовали ужасные имена переменных, вам понадобится гораздо больше времени, чтобы понять, как это исправить, чем нужно. 

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

72
Rick Teachey

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

Вы будете получать .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"

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

40
MattB

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

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

27
user4039065

Обратите внимание, что ниже я сравниваю подход Select (тот, который ОП хочет избежать) с подходом 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

Надеюсь, это поможет немного прояснить ситуацию.

26
Francesco Baruchelli

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

Вот как вы можете избежать Select и Activate в следующих случаях:


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

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 . Всякий раз, когда вы копируете диапазоны, иногда на рабочий лист не ссылаются, и поэтому 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
19
Vityata

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

Например:

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

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

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

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

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

13
user1644564

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

Почему бы не вызвать .Activate, .Select, Selection, ActiveSomething методы/свойства

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

Тем не менее, это определение разрешает ситуации, к которым они обращаются:

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

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

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

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

5
LFB

Быстрый ответ:

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

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

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

► Например, если вы хотите использовать кодовое имя «Sheet3», вы можете установить переменную, равную свойству кодового имени этого листа.

  • Пример valTwo = Sheets("Sheet3").Codename

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

4
FinPro.Online

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

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

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

4
Eleshar

Я заметил, что ни в одном из этих ответов не упоминается свойство .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
PGCodeRider

Это пример, который очистит содержимое ячейки «A1» (или более, если тип выбора - xllastcell и т.д.). Все сделано без выбора ячеек. 

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

Я надеюсь, что это помогает кому-то.

0
marionffavp