Как избежать использования Select в Excel VBA
Я много слышал об понятном отвращении к использованию .Select
VBA в Excel, но не знаю, как этого избежать. Я обнаружил, что мой код можно было бы использовать повторно, если бы я мог использовать переменные вместо Select
функций. Однако я не уверен, как относиться к вещам (например ActiveCell
, и т. Д.), Если они не используются Select
.
Я нашел эту статью о диапазонах и этот пример о преимуществах отказа от использования select , но я не могу найти ничего о том, как это сделать .
Ответы
Некоторые примеры того, как избежать выбора
Используйте 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
Передайте диапазоны вашим Sub
s и Function
s как переменные 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
Это небольшой пример того, что возможно.
Две основные причины .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
Я добавлю один небольшой акцент ко всем превосходным ответам, данным ранее:
Вероятно, самое важное, что вы можете сделать, чтобы избежать использования 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
столбцы могут быть перемещены все , что вам нравится, и ваш код будет продолжать работать нормально.
Я собираюсь дать короткий ответ, поскольку все остальные дали длинный.
Вы получите .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"
Существуют различные комбинации этих методов, но это была бы общая идея, выраженная как можно короче для нетерпеливых людей вроде меня.
«... и я обнаружил, что мой код можно было бы использовать повторно, если бы я мог использовать переменные вместо функций выбора».
Хотя я не могу придумать ничего, кроме изолированной горстки ситуаций, где .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.)
Избегайте 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
Обратите внимание, что ниже я сравниваю подход 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
Всегда указывайте книгу, лист и ячейку / диапазон.
Например:
Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)
Поскольку конечные пользователи всегда будут просто нажимать кнопки, и как только фокус перемещается с рабочей книги, с которой хочет работать код, все идет совершенно не так.
И никогда не используйте индекс книги.
Workbooks(1).Worksheets("fred").cells(1,1)
Вы не знаете, какие еще книги будут открыты, когда пользователь запустит ваш код.
Эти методы довольно стигматизированы, поэтому взять на себя инициативу Vityata и Jeeped, чтобы провести черту на песке:
Вызов .Activate
, .Select
, Selection
, ActiveSomething
методы / свойства
В основном потому, что они вызываются в первую очередь для обработки пользовательского ввода через пользовательский интерфейс приложения. Поскольку это методы, вызываемые, когда пользователь обрабатывает объекты через пользовательский интерфейс, они записываются макро-регистратором, и поэтому их вызов либо хрупкий, либо избыточный в большинстве ситуаций: вам не нужно выбирать объект, чтобы Selection
сразу после этого выполнить действие .
Однако это определение разрешает ситуации, в которых они требуются:
Когда звонить .Activate
, .Select
, .Selection
, .ActiveSomething
методы / свойства
В основном, когда вы ожидаете, что конечный пользователь будет играть роль в выполнении.
Если вы разрабатываете и ожидаете, что пользователь выберет экземпляры объекта для обработки вашего кода, тогда .Selection
или .ActiveObject
являются подходящими.
С другой стороны, .Select
и .Activate
используются, когда вы можете сделать вывод о следующем действии пользователя и хотите, чтобы ваш код направлял пользователя, возможно, сэкономив ему / ей время и количество щелчков мыши. Например, если ваш код только что создал новый экземпляр диаграммы или обновил его, пользователь может захотеть проверить его, и вы можете вызвать .Activate
его или его лист, чтобы сэкономить время пользователя на его поиск; или если вы знаете, что пользователю потребуется обновить некоторые значения диапазона, вы можете программно выбрать этот диапазон.
ИМХО использование .select
исходит от людей, которые, как я, начали изучать VBA по необходимости, записывая макросы, а затем изменяя код, не осознавая этого, .select
а затем selection
- просто ненужные посредники.
.select
можно избежать, как многие уже писали, напрямую работая с уже существующими объектами, что позволяет использовать различные косвенные ссылки, такие как вычисление i и j сложным способом, а затем редактирование ячейки (i, j) и т. д.
В противном случае нет ничего неявно неправильного с .select
самим собой, и вы можете легко найти применение для этого, например, у меня есть электронная таблица, которую я заполняю датой, активирую макрос, который делает с ним некоторую магию, и экспортирует его в приемлемом формате на отдельном листе, который однако требует некоторых заключительных ручных (непредсказуемых) вводов в соседнюю ячейку. Вот и настал момент, когда .select
это избавляет меня от лишних движений мыши и щелчков.
Чтобы избежать использования этого .Select
метода, вы можете установить переменную, равную желаемому свойству.
► Например, если вы хотите ввести значение, Cell A1
вы можете установить переменную, равную свойству value этой ячейки.
- пример
valOne = Range("A1").Value
► Например, если вам нужно кодовое you could set a variable equal to the
имя свойства Sheet3 Codename для этого рабочего листа.
- пример
valTwo = Sheets("Sheet3").Codename
Я заметил, что ни в одном из этих ответов не упоминается свойство .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.
Это не значит, что они «лучше», чем вышеперечисленные варианты, а просто перечисление альтернатив.
Как избежать копипаста?
Посмотрим правде в глаза: этот часто появляется при записи макросов:
Range("X1").Select
Selection.Copy
Range("Y9).Select
Selection.Paste
А человек хочет только:
Range("Y9").Value = Range("X1").Value
Поэтому вместо использования копипаста в макросах VBA я бы посоветовал следующий простой подход:
Destination_Range.Value = Source_Range.Value
Работая с функцией .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
Основная причина никогда не использовать 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», вы можете копировать и вставлять код из одной книги в другую, и это обычно будет работать с минимальными необходимыми изменениями, если таковые имеются.