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

470

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

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

  • 7
    Важно отметить, что существуют случаи, когда использование Select и / или ActiveSheet т. Д. ActiveSheet т. Д. Совершенно неизбежно. Вот пример, который я нашел: stackoverflow.com/questions/22796286/…
  • 4
    @RickTeachey Я думаю, что Сиддхарт избежал этого, так какой у тебя смысл?
Показать ещё 4 комментария
Теги:
excel-vba
excel

13 ответов

477
Лучший ответ

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

Использовать переменные 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

Если вы хотите работать с ActiveSheet, для ясности лучше всего быть явным. Но будьте осторожны, так как некоторые методы Worksheet изменяют активный лист.

Set rng = ActiveSheet.Range("A1")

Опять же, это относится к активной книге. Если вы специально не хотите работать только с ActiveWorkbook или ThisWorkbook, лучше также Workbook переменную Workbook.

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

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

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

  • 0
    почему мы должны использовать 'Sub ClearRange (r как Range)' вместо прямого .ClearContents?
  • 3
    добавив к этому блестящему ответу, что для работы с диапазоном вам не нужно знать его фактический размер, если вы знаете верхний левый rng1(12, 12) ... например, rng1(12, 12) будет работать, даже если для rng1 было установлено значение [A1:A10] только.
Показать ещё 11 комментариев
175

Две основные причины, по которым следует избегать .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
  • 12
    Это хороший ответ, но я упускаю эту тему, когда нам действительно нужно активировать. Все говорят, что это плохо, но никто не объясняет ни одного случая, когда есть смысл его использовать. Например, я работал с двумя рабочими книгами и не мог запустить макрос на одной из рабочих книг, не активировав ее сначала. Не могли бы вы уточнить немного, может быть? Кроме того, если, например, я не активирую листы при копировании диапазона с одного листа на другой, то при выполнении программы он, по-видимому, активирует соответствующие листы в любом случае, неявно.
  • 0
    Я считаю, что вам иногда может понадобиться сначала активировать лист, если вам нужно вставить или отфильтровать данные на нем. Я бы сказал, что лучше избегать активации, насколько это возможно, но есть случаи, когда вам нужно это сделать. Так что продолжайте активировать и выбирать до абсолютного минимума согласно ответу выше.
Показать ещё 3 комментария
71

Одна небольшая точка внимания я добавлю к всем превосходным ответам, приведенным выше:

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

  • 5
    Дискуссия о том, хороши ли названные диапазоны или нет, дизайн электронных таблиц продолжается - я твердо в лагере. По моему опыту, они увеличивают количество ошибок (для обычных пользователей, которым не нужен код).
  • 2
    Я даже не знал, что есть дебаты.
Показать ещё 10 комментариев
38

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

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

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

28

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

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

  • 11
    Selection может быть любым на рабочем листе, поэтому можно сначала проверить тип объекта, прежде чем назначить его переменной, поскольку вы явно объявили его как Range .
26

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

Sub Macro1()
    SetValue ActiveCell.Address, "foo"
End Sub

Надеюсь, это поможет немного разобраться.

  • 1
    Спасибо за отличный ответ так быстро. Значит ли это, что если я обычно добавляю ячейки к диапазону, называю диапазон и перебираю его, я должен сразу перейти к созданию массива?
  • 0
    Я не уверен, что понимаю, что вы имеете в виду, но вы можете создать Range с помощью одной инструкции (например, Range ("B5: C14")), и вы даже можете установить его значение сразу (если оно должно быть одинаковым для каждая ячейка в диапазоне), например Range ("B5: C14"). Значение = "abc"
19

Избегание 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

To:

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

Если вы хотите скопировать диапазон между листами:

From:

Sheets("Source").Select
Columns("A:D").Select
Selection.Copy
Sheets("Target").Select
Columns("A:D").Select
ActiveSheet.Paste

To:

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 для чего-либо?

Единственным моментом, когда вы можете быть оправданы использование .Activate и .Select, является то, что вы хотите удостовериться, что конкретный рабочий лист выбран по визуальным соображениям. Например, ваш Excel всегда будет открываться с выбранным первым листом обложки, отбрасывая, какой из них был активным, когда файл был закрыт. Таким образом, что-то вроде этого абсолютно нормально:

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

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

Например:

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

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

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

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

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

  • 3
    Имена рабочих листов тоже могут меняться, вы знаете. Вместо этого используйте кодовые имена.
5

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

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

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

  • 0
    Хотя вы правы, в select есть, по крайней мере, одно явное замечание: оно медленное. Действительно очень медленный по сравнению со всем, что происходит в макросе.
4

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

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

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

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

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

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

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

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

4

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

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

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

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

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

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

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

1

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

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

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

0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню