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

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

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

13 ответов


некоторые примеры того, как избежать 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

если вы do С 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")

если вы do С 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

Pass диапазоны для вашего Subи Functionв качестве переменных диапазона

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 etc... следует избегать

  1. это замедляет ваш код.
  2. обычно это основная причина ошибок во время выполнения.

как нам избежать этого?

1) напрямую работать с соответствующими объектами

рассмотрим этот код

Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"

этот код также может быть записан as

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 contain, и что делает процедура.

почему это важно? Частично потому, что другим людям легче понять это, но даже если вы единственный человек, который когда-либо увидит или использует ваш код, вы все равно должны использовать именованные диапазоны и good имена переменных, потому что ВЫ ЗАБУДЕТЕ что вы собирались сделать с ним через год, и вы будете тратить 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 столбцы можно перемещать все, что вам нравится, и ваш код будет работать просто отлично.


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

вы получите .выбрать и. активируйте макросы при записи и повторном использовании. Когда вы.выберите ячейку или лист он просто делает его активным. С этого момента, когда вы используете неквалифицированные ссылки, такие как 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 (тот, который 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 как:

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

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


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

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


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

From (код, сгенерированный с помощью макроса recorder):

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 всегда открывать с крышкой листе выбрана первой, disregading которых был объект activesheet, когда файл был закрыт. Таким образом, что-то вроде этого абсолютно ok:

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

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

например:

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

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

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

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

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


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

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

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


Короткий Ответ:

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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


я заметил, что ни один из этих ответов не говоря уже о .Свойство Смещения. Это также можно использовать, чтобы избежать использования Select действие при манипулировании определенными ячейками, особенно в отношении выбранной ячейки (как упоминает OP с ActiveCell).

вот несколько примеров.

Я также предположу, что "ActiveCell" - это J4 не.

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

  • это изменит ячейку J6 значение 12
  • минус -2 ссылался бы на J2

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

  • это скопирует ячейку в k4 to L4.
  • обратите внимание, что "0" не требуется в параметре offset,если не требуется (, 2)
  • аналогично предыдущему примеру минус 1 будет i4

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

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

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