Excel медленно работает макрос

 

KL

Пользователь

Сообщений: 2186
Регистрация: 01.01.2013

e_artem, если позволите, в вашем разделе Оптимизация есть весьма сомнительные/спорные обобщения :)

1) избегать формул массива потому, что некоторые могут подвесить расчеты от непонимания того, как они действуют, — все равно, что избегать VBA потому, что можно по незнанию попасть в бесконечный цикл. Вопреки поверьям, формулы массива не медленнее, чем отдельные формулы их составляющие если сумма операций одна и та же. Формулы массива менее эффективны лишь тогда, когда алгоритм не оптимален: а. повторные расчеты, которых можно избежать, вынеся на лист как отдельные формулы, б. как частный случай, бинарные условные расчеты типа СУММПРОИЗВ(…), где нет возможности прекратить расчеты после нахождения искомого или где не был ограничен использованный диапазон. Точно также не все формулы массива длинные и непонятные :)

2) волатильность — не абсолютное зло, ее следует избегать в специфическом случае, когда сумма времен пересчета всех зависимых от нее формул достигает уровня создающего неудобства в работе с файлом, например 1 секунда и более (но последнее субъективно). Отказ от волатильных функций в других случаях может означать ненужные ограничения. Для примера, волатильны все функции времени и случайных чисел, а также СМЕЩ, ДВССЫЛ, ЯЧЕЙКА, ИНФОРМ и некоторые частные случаи неволатильных функций.

3) совсем непонятно избегание имен :) единственный недостаток я могу себе представить — это пересчет каждого упоминания имени в случае если зависимая формула пересчитывается, но это может быть проблемой лишь в случае «тяжелых» расчетов (например точного поиска или поиска по условиям) хранимых в имени. В этом смысле это то же, что и мой подпункт а. в пункте 1) этого списка.

6) злоупотреблять конечно не стоит, но на всякий случай, использование целых столбцов в функциях точного поиска и функции ИНДЕКС абсолютно не влияет на эффективность формулы. В случае функций неточного поиска, влияние нематериально даже при множестве повторов ввиду бинарного алгоритма. Реально известная проблема с этим — опять таки бинарные условные формулы типа СУММПРОИЗВ(…)

Изменено: KL20.01.2016 14:20:39

Хитрости »

20 Январь 2016              61464 просмотров


Рано или поздно у пишущих на Visual Basic for Applications возникает проблема — код хоть и облегчает жизнь и делает все автоматически, но очень долго. В этой статье я решил собрать несколько простых рекомендаций, которые помогут ускорить работу кода VBA, при этом в некоторых случаях весьма внушительно — в десятки, а то и больше, раз. Основной упор в статье сделан на начинающих, поэтому в начале статьи приводятся самые простые методы оптимизации. Более «глубокие» решения по оптимизации кода приведены в конце статьи, т.к. для применения данных решений необходим достаточный опыт работы в VB и сходу такие методы оптимизации кому-то могут быть непонятны.

  1. Если в коде есть много всяких Activate и Select, тем более в циклах — следует немедленно от них избавиться. Как это сделать я писал в статье: Select и Activate — зачем нужны и нужны ли?
  2. Обязательно на время выполнения кода отключить:
    • автоматический пересчет формул. Чтобы формулы не пересчитывались при каждой манипуляции на листе во время выполнения кода — это может дико тормозить код, если формул много:
      Application.Calculation = xlCalculationManual

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

      Range("A1:C60").Calculate
    • обновление экрана, чтобы действия по изменению значений ячеек и пр. не мелькали. Зачем это надо? Т.к. все эти действия обращаются к графическому процессору и заставляют его трудиться для перерисовки экрана — это может значительно тормозить код:
      Application.ScreenUpdating = False
    • На всякий случай отключаем отслеживание событий. Нужно для того, чтобы Excel не выполнял никаких событийных процедур, которые могут быть в листе, в котором производятся изменения. Как правило это события изменения ячеек, активации листов и пр.:
      Application.EnableEvents = False
    • Если книга выводилась на печать или выводится на печать в процессе выполнения кода, то лучше убрать разбиение на печатные страницы.
      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = False

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

    • На всякий случай можно отключить отображение информации в строке статуса Excel(в каких случаях там вообще отображается информация и зачем можно узнать в статье: Отобразить процесс выполнения). Хоть это и не сильно поедает ресурсы — иногда может все же ускорить работу кода:
      Application.StatusBar = False

    Главное, что следует помнить — все эти свойства необходимо включить обратно после работы кода. Иначе могут быть проблемы с работой внутри Excel. Например, если забыть включить автопересчет формул — большинство формул будут пересчитывать исключительно принудительным методом — после нажатия сочетания клавиш Shift+F9. А если забыть отключить обновление экрана — то есть шанс заблокировать себе возможность работы на листах и книгах. Хотя по умолчанию свойство ScreenUpdating и должно возвращаться в True, если было отключено внутри процедуры — лучше не надеяться на это и привыкать возвращать все свойства на свои места принудительно. По сути все это сведется к нескольким строкам:

    'Возвращаем обновление экрана
    Application.ScreenUpdating = True
    'Возвращаем автопересчет формул
    Application.Calculation = xlCalculationAutomatic
    'Включаем отслеживание событий
    Application.EnableEvents = True

    Как такой код выглядит на практике. Предположим, надо записать в цикле в 10 000 строк значения:

    Sub TestOptimize()
    'отключаем обновление экрана
    Application.ScreenUpdating = False
    'Отключаем автопересчет формул
    Application.Calculation = xlCalculationManual
    'Отключаем отслеживание событий
    Application.EnableEvents = False
    'Отключаем разбиение на печатные страницы
    ActiveWorkbook.ActiveSheet.DisplayPageBreaks = False
     
    'Непосредственно код заполнения ячеек
    Dim lr As Long
    For lr = 1 To 10000
        Cells(lr, 1).Value = lr 'для примера просто пронумеруем строки
    Next
     
    'Возвращаем обновление экрана
    Application.ScreenUpdating = True
    'Возвращаем автопересчет формул
    Application.Calculation = xlCalculationAutomatic
    'Включаем отслеживание событий
    Application.EnableEvents = True
    End Sub

    Разрывы печатных страниц можно не возвращать — они тормозят работу в любом случае.

  3. Следует избегать циклов, вроде Do While для поиска последней ячейки. Часто такую ошибку совершают начинающие. Куда эффективнее и быстрее вычислять последнюю ячейку на всем листе или в конкретном столбце без этого тормозного цикла Do While. Я обычно использую
    lLastRow = Cells(Rows.Count,1).End(xlUp).Row

    другие варианты определения последней ячейки я детально описывал в статье: Как определить последнюю ячейку на листе через VBA?

Для более опытных пользователей VBA я приведу несколько решений по оптимизации кодов в различных ситуациях:

  • Самая хорошая оптимизация кода, если приходится работать с ячейками листа напрямую, обрабатывать их и, возможно, изменять значения, то быстрее все обработки делать в массиве и разом выгружать на листе. Например, код выше по заполнению ячеек номерами будет в этом случае выглядеть так:
    Sub TestOptimize_Array()
    'Непосредственно код заполнения ячеек
    Dim arr, lr As Long
    'запоминаем в массив одним махом все значения 10000 строк первого столбца
    arr = Cells(1, 1).Resize(10000).Value
    'если нужно заполнение для двух и более столбцов
    'arr = Cells(1, 1).Resize(10000, 2).Value
    'или 
    'arr = Range(Cells(1, 1),Cells(10000, 2)).Value
    'или автоматически вычисляем последнюю ячейку и заносим в массив данные, начиная с ячейки А3
    'llastr = Cells(Rows.Count, 1).End(xlUp).Row 'последняя ячейка столбца А
    'arr = Range(Cells(3, 1),Cells(llastr, 2)).Value
    For lr = 1 To 10000
        arr(lr,1) = lr 'заполняем массив порядковыми номерами
    Next
    'Выгружаем обработанный массив обратно на лист в те же ячейки
    Cells(1, 1).Resize(10000).Value = arr
    End Sub

    Но здесь следует учитывать и тот момент, что большие массивы могут просто вызвать переполнение памяти. Наиболее актуально это для 32-битных систем, где на VBA и Excel выделяется памяти меньше, чем в 64-битных системах

  • Если используете быстрый ЕСЛИ — IIF, то замените его на IF … Then … Else
  • Так же лучше вместо Switch() и Choose() применить тот же IF … Then … Else
  • В большинстве случаев проверять строку на «не пусто» лучше через Len(), чем прямое сравнение с пустотой: Len(s)=0 вместо s = «». Связано с тем, что работа со строками значительно медленнее, чем с числовыми данными и Len по сути не подсчитывает длину переменной, а берет это число непосредственно уже готовое из памяти. При сравнении же текста с пустой строкой(«»), VBA сначала создает в памяти переменную нулевой длинны, а уже потом сравнивает с ней наш текст. Поэтому в некоторых случаях так же ускоряет сравнение и в таком виде: s = vbNullString
  • Не применять объединение строк без необходимости. Например, s = «АВ», будет быстрее, чем: s =»А» & «В»
  • Не применять сравнение текстовых величин напрямую. Лучше применить встроенную функцию StrComp:
    If s <> s1 Then будет медленнее, чем
    If StrComp(s, s1, vbBinaryCompare) <> 0
    и тем более, если при сравнении необходимо не учитывать регистр:
    If LCase(s) <> LCase(s1) Then будет медленнее, чем
    If StrComp(s, s1, vbTextCompare) <> 0
  • Циклы For … Next в большинстве случаев работает быстрее, чем цикл Do … Lоор
  • Избегать присвоения переменным типа Variant. Хоть соблазн и велик — этот тип забирает много памяти и в дальнейшем замедляет работу кода. Так же для объектных переменных следует избегать по возможности безликого глобального типа Object и применять конкретный тип:
    Dim rRange as Object, wsSh as Object

    будет медленнее работать, чем:

    Dim rRange as Range, wsSh as Worksheet

    Причина в том, что при объявлении As Object мы не даем VBA практически никакой информации о типе данных, кроме того, что это какой-то объект. И VBA приходится «на лету» внутри кода при каждом обращении к такой переменной определять её конкретный тип(Range, Worksheet, Workbook, Chart и т.д.). Что опять же занимает время.

  • Если работаете с массивами, то можно при объявлении указать это явно:

    вместо

    Такая инициализация происходит быстрее.
    А еще лучше будет при этом еще и тип данных сразу присвоить:

    Dim arr() as string, arr2() as long

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

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


Статья помогла? Поделись ссылкой с друзьями!

  Плейлист   Видеоуроки


Поиск по меткам



Access
apple watch
Multex
Power Query и Power BI
VBA управление кодами
Бесплатные надстройки
Дата и время
Записки
ИП
Надстройки
Печать
Политика Конфиденциальности
Почта
Программы
Работа с приложениями
Разработка приложений
Росстат
Тренинги и вебинары
Финансовые
Форматирование
Функции Excel
акции MulTEx
ссылки
статистика

I have an issue running an Excel Macro. I have read a lot of threads and tried all of the suggestions but no change in performance. I have run the same file/macro on 2 machines, details below. No matter what change on Laptop 2 (problematic machine), iteration time was unchanged.

Laptop 1:
Dell Precision M4800
2015
Intel i7-4800MQ @2.70GHz, 32GB 64-bit
Windows 10 Enterprise
Version 1909
OS 18363.1734
Excel 16.0.13127.21734 64-bit

Laptop 2:
Macbook Pro 15″ mid-2020
2021
Intel i9-9980HK @2.4GHz, 64GB 64-bit
Windows 10 Home
Version 20H2
OS 19042.1237
Excel 16.0.14326.20384 64-bit

Running the same file, same macro on both machines
1.5M+ calculations per iteration (cell formulas), 7 output cells
Laptop 1 = 11 seconds per iteration; Outlook, multiple Excel files open, Teams chat, Teams meeting, 3 sessions File Explorer, 2 sessions Chrome (multiple/many tabs each), has not rebooted in over a week (I know.. hard to believe); noticed slower operation of other programs, no change in performance of macro with other programs open
Laptop 2 = 24 seconds per iteration; no additional programs open or running, freezes system

Laptop 2:
Rebooted
Ran in Normal view (was not in Page Layout)
Disabled hardware acceleration
Ran with Wifi on and off
Ran clean boot
Ran diagnostic mode
Disabled all startup programs

Ran repair on 32-bit version
Uninstalled 32-bit/re-installed 64-bit Office 365

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

' Процедура : TurnOffFunctionality
' Источник  : www.planacademy.ru
' Назначение: Отключает автоматические вычисления, обработку событий и обновление экрана
Public Sub TurnOffFunctionality()
    ' Все расчеты переводим в ручной режим
    Application.Calculation = xlCalculationManual
    ' Отключаем статусную строку
    Application.DisplayStatusBar = False
    ' Отключаем события
    Application.EnableEvents = False
    '  Отключаем показ разбиения листа на печатные страницы 
    If Workbooks.Count Then
      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = False 
    End If
    ' Больше не обновляем страницы после каждого действия  
    Application.ScreenUpdating = False
    ' Отключаем сообщения Excel
    Application.DisplayAlerts = False 
End Sub

' Процедура : TurnOnFunctionality
' Источник  : www.planacademy.ru
' Purpose   :  Включает автоматические вычисления, обработку событий и обновление экрана 
Public Sub TurnOnFunctionality()
    Application.Calculation = xlCalculationAutomatic
    Application.DisplayStatusBar = True
    Application.EnableEvents = True
     If Workbooks.Count Then
      ActiveWorkbook.ActiveSheet.DisplayPageBreaks =  True 
    End If 
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
End Sub

Использовать эти процедуры можно следующим образом:

Sub Main()

    ' Выключить вначале
    TurnOffFunctionality
    
    ' Ваш основной код здесь
    
    ' Включить в конце
    TurnOnFunctionality

End Sub

Иногда, когда выполняется макрос, процедура не доходит до строки включения функций “TurnOnFunctionality”. Это может произойти из-за ошибки или из принудительной остановки кода в определенный момент. Если это произойдет, вы можете снова включить все функции, выбрав процедуру TurnOnFunctionality в окне радактирования кода и нажав клавишу F5.

Никогда не используем функцию Select

При копировании данных в Excel VBA не используйте метод Select-никогда! Большая ошибка, которую делают новички VBA, – это думают, что им нужно выбрать ячейку или диапазон, прежде чем ее скопируют.
Например:

shRead.Activate
shRead.Range("A1").Select
shWrite.Activate
Selection.Copy Activate.Range("H1")

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

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

Часто можно увидеть, что Select используется во многих примерах в интернете. Но вам не нужно использовать его для копирования ячеек – никогда!

Общие рекомендации

Оптимизация
1) Желательно избегать формул массивов (по возможности)
2) Избегать волатильных формул (по возможности) (условное форматирование, как пример волатильности)
3) Использовать структурированные таблицы и именованные диапазоны
4) Заменять неиспользуемые формулы значениями
5) Хранить все данные для расчета на одном листе (по возможности)
6) Избегать в формулах ссылки на целые столбцы
7) использовать =IFERROR вместо IF + ISERR/ISERROR
8) использовать MAX(A1;0) вместо IF(A1>0;A1;0)
9) использовать INDEX+MATCH вместо VLOOKUP
10) использовать — для конвертации лог.значение в 0/1
11) использовать A1*0.1 чем A1/10
VBA
12) Использовать оператор if else, вместо IIF
13) Использовать Long т.е. Long лучше Вуte, Integer, конечно, variant
14) Применять if else, чем switsh; shoose
15) Лучше проверять строку так len(s)=0, чем s = “”
16) Лучше инициализировать строку так s =vbnullstring, чем s =””
17) Если возможно писать так s = “АВ”, чем s =”А” & “В”
18) Вставлять строку в текст так Мid$(а,3,4)=”like”. чем s = left$(8,2) & “like” & mid(з,7)
19) Сравнивать строки так if strcomp(s1,s2,vbtextcompare) =0, чем if ucase(s1)=ucase(s2)
20) Лучше использовать $-функцию, т.е. left$ лучше left.
21) Цикл for …next. работает быстрее do…lоор
22) Точнее объявлять объекты: as commandbutton лучше as control, еще лучше as object
23) Цикл for Еасh..next лучше, чем цикл for…next семейств объектов
24) Цикл for …next лучше, чем цикл fог Еасh…next: для массивов.
25) Использовать оператор With при обращении к элементам сходной иерархической модели
Для общих (расшаренных) книг
26) Удалять пользовательские представления Вид-Представления-Удалить
27) Снять пометки с Параметров печати и Фильтров , так Рецензирование-Доступ к книге-Параметры печати и Фильтры
28) Периодически отключать общий доступ, сохранять книгу, отключать общий доступ и снова сохранять
29) Удостовериться, что книга открывается в режиме просмотра Вид-Обычный (для того, чтобы избежать обращения к сетевому принтеру с соответствующими временными последствиями)
30) Перелить одну книгу в другую путем копирования не листа, а содержимого в новую книгу со вставкой сначала значения, потом формулы, потом форматы и макросы через импорт экспорт перенести в новую книгу
31 ) Удалить избыточное форматирование

Ускоряем работу макроса в Excel

Чем больше познаём мы макросы, тем интересней выгледят наши программы. А бывает, что их выполнение происходит очень долго, при сложных и долгих математических расчётах или составлении каких-то отчётов и табилц. В ходе выполнения макроса на мониторе происходит мелькание различных окон, открытие и закрытие книг, и прочая светомузыка. Для того чтобы этого не происходило, и чтобы время выполнение нашего макроса сократить раз в 100, можно воспользоваться командами описанные ниже.

Application.ScreenUpdating

Application.ScreenUpdating — отвечает за обновление экрана и может принимать два значения — False (обновление экрана отключено) и True (обновление экрана включено). В коде это обычно прописывается в том месете, где происходит мелькание различных окон или видно как производится расчёт и происходит заполнение таблицы. Ниже показан пример заполнения ячеек, и данную команду вставили в начало и конец макроса, т.е. сначала отключаем обновление экрана, а потом включаем обновление экрана. При такой записи мы не увидим процесс заполнения ячеек. А вот если убрать эти команды, то мы сможем наблюдать за процессом заполнения этих ячеек.

Sub Primer()

Application.ScreenUpdating = False

For a = 1 To 100

For b = 1 To 100

Cells(a, b) = «Пример»

Next b

Next a

Application.ScreenUpdating = True

End Sub

Application.Calculation

Application.Calculation — отвечает за автоматический расчёт в книге Excel и может принимать два значения — xlCalculationManual(ручной расчёт) и xlCalculationAutomatic (автоматический расчёт — по умолчанию установлен в Excel). Но тут есть одна осторожность, если вы перевели Excel в ручной расчёт, и в макросе произошла ошибка и он так и не выполнился до конца — т.е. не включился автоматический расчёт формул, то все ваши вычисления в дальнейшем будут в пустую. Так как формулы не будут автоматически пересчитываться, Excel превратиться в обычную таблицу. Но данная команда играет одну из основных ролей в быстроте выполнеия макроса. Вообщем лучше сделать код, который исключает ошибки, чтобы макрос полюбому выполнился и Excel перевёлся в автоматический расчёт. Если у Вас имеется большая таблица с многочисленными формулами, и часть вычислений вы производите при помощи макросов, то для быстроты выполнения расчётов разумно в начало и конец кода поместить команду Application.Calculation.

Sub Primer2()

Application.Calculation = xlCalculationManual

……………….

……………….

……………….

Application.Calculation = xlCalculationAutomatic

End Sub

Application.EnableEvents

Application.EnableEvents — команда отвечающая за выполнение сторонних событий. Эту команду мы уже затрагивали в этом уроке. И она также может принимать два значения — это False (отключить собтие) и True (включить выполнение промежуточных событий). Но теперь ещё известно, что она играет значительную роль в скорости выполнения некоторых кодов макроса. Пример можно взять из Урока №23.

Private Sub Worksheet_Change(ByVal Target As Range)

Application.EnableEvents = False

Target.Cells = «Привет»

Application.EnableEvents = True

End Sub

ActiveSheet.DisplayPageBreaks

ActiveSheet.DisplayPageBreaks — отображение границ листа. Может принимать два значения — False (отключить отображение границ) и True (включить отображение границ). Не знаю как это помогает на скорости выполнения макроса, лично я этого не ущутил, но некоторые говорят, что помогает. Я вообще не люблю когда отображаются границы листа, мне кажется, что это нужно только при распечатке. Помещать этот код можно в начало и конец макроса.

Private Sub Worksheet_Change(ByVal Target As Range)

ActiveSheet.DisplayPageBreaks = False

…………………….

…………………….

…………………….

ActiveSheet.DisplayPageBreaks = True

End Sub

Application.DisplayStatusBar

Application.DisplayStatusBar — строка состояния. Может принимать два значения — False (отключить строку состояния) и True (включить строку состояния). При выполнении макросов в строке состояния отображаются все происходяще события. Для того чтобы не тратить время на просчёт событий и прорисовку их в статусбаре, отключаем её на время выполнения макроса, и включаем её когда макрос закончил выполняться.

Private Sub Worksheet_Change(ByVal Target As Range)

Application.DisplayStatusBar = False

…………………….

…………………….

…………………….

Application.DisplayStatusBar = True

End Sub

Application.DisplayAlerts

Application.DisplayAlerts — команда отвечающая за события в Excel. Может принимать два значения — False (отключаем запросы Excel) и True (включаем события Excel). Это чень интересная и полезная команда при помощи которой можно отключить запросы Excel, например, чтобы он не спрашивал нужно ли сохранить изменения в книге, или отключить запрос на совместимость версий Excel. Ниже приведён пример, в котором книга закрывает сама себя, при этом независимо от того внесли вы изменения или нет в книгу, при закрытии книги вам не поступит запрос «Сохранить изменения», а книга просто закроется без сохранения и уведомления пользователя.

Sub Primer()

Application.DisplayAlerts = False

ThisWorkbook.Close

Application.DisplayAlerts = True

End Sub

Глобальное ускорение

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

sub Primer()

Application.ScreenUpdating = False

Application.Calculation = xlCalculationManual

Application.EnableEvents = False

Application.DisplayStatusBar = False

Application.DisplayAlerts = False

…………………….

…………………….

…………………….

…………………….

Application.ScreenUpdating = True

Application.Calculation = xlCalculationAutomatic

Application.EnableEvents = True

Application.DisplayStatusBar = True

Application.DisplayAlerts = True

End Sub

Скрытые перемещения

Я решил выделить отдельный урок, в котором будут описаны относительные команды скрытых перемещений по книге. Например каким образом возможно выполнить перемещение курсора на 3 столбца влево и на 6 строк вниз относительно активной ячейки. Это бывает очень удобно когда вы работаете с одними и теми же данными, с одинаковыми таблицами, из которых Вам надо выделить только определённые значения, скопировать их или наоборот оставить только их, а всё остальное удалить. При этом у Вас всё время выполняется одна и таже операция, которая уже надоела. Почему бы не написать готовый макрос, который будет работать и опираться на определённую ячейку таблицы — активную ячейку. Именно это я и решил собрать всё в одном уроке — Скрытые перемещения.

Относительные перемещения

Рассмотрим команду относительного перемещения, которая называется:

Offset(RowOffset, ColumnOffset)

RowOffset — это на сколько строк вверх или вниз необходимо сделать перемещение. Может принимать как положительные значения (при перемещении вниз), так и отрицательные значения (при перемещении вверх);
ColumnOffset — это на сколько столбцов влево или вправо необходимо сделать перемещение. Может принимать как положительные значения (при перемещении вправо), так и отрицательные значения (при перемещении влево);

Sub Primer()

ActiveCell.Offset(0, 2).Select ‘1

ActiveCell.Offset(4, 0).Select ‘2

ActiveCell.Offset(-2, 0).Select ‘3

ActiveCell.Offset(0, -2).Select ‘4

End Sub

В первом случае курсор выделит ячеку находящуюся на 2 ячейки вправо от активной ячейки. Во втором случае выделится ячейка находящаяся на 4 строки вниз от предыдущей — активной ячейки. В третьем случае выделится ячейка находящаяся на две строки выше от предыдущей — активной ячейки. И в последнем случае выделится ячейка находящаяся на два столбца влево от предыдущей — активной ячейки.

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

Sub Primer()

Columns(4).Offset(0, 2).Select

End Sub

Перемещаемся по листам не зная их имён

Иногда возникает необходимость выполнить перемещение по листам, но при этом их имена постоянно меняются или Вы делаете такую программу, в которой Вы заранее не знаете имена листов, но перемещаться в ней по этим листам как-то надо. Для этого можно воспользоваться записью относительной:

Sheets(X)

Sheets — это лист;
(X) — это номер листа;

Ниже на примере показано как можно выделить лист №2, и не важно какое у него имя, второй по счёту лист будет выделен:

Sub Primer()

Sheets(2).Select

End Sub

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

Sub Primer()

Sheets(2).Name = «Пример»

End Sub

Крайние перемещения

Возможно кто-то уже задавался вопросом: «Как определить край таблицы или последний заполненный столбец, строку?» Вот тут я и покажу как определить последнюю заполненную ячейку. Зная эту ячейку можно определить конечную координату таблицы, которую в последствии можно успешно обработать. Ниже показан пример выделения конечных ячеек относительно текущей.

Sub Primer()

Selection.End(xlToRight).Select ‘1

Selection.End(xlUp).Select ‘2

Selection.End(xlToLeft).Select ‘3

Selection.End(xlDown).Select ‘4

End Sub

1 — это выделение крайней правой яейки;
2 — это выделение самой верхней ячейки;
3 — это выделение крайней левой ячейки;
4 — это выделение нижней заполненной ячейки;

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

Sub Primer()

Selection.End(xlToRight) = «Пример»

End Sub

Выделения относительно активной ячейки

Выделить вниз до первой или последней заполненной ячейки (равносильно нажатию Ctrl+Shift+Down)

Sub CtrlShiftDown()

Range(ActiveCell, ActiveCell.End(xlDown)).Select

End Sub

Выделить вверх до первой или последней заполненной ячейки (равносильно нажатию Ctrl+Shift+Up)

Sub CtrlShiftUp()

Range(ActiveCell, ActiveCell.End(xlUp)).Select

End Sub

Выделить вправо до первой или последней заполненной ячейки (равносильно нажатию Ctrl+Shift+Right)

Sub CtrlShiftRight()

Range(ActiveCell, ActiveCell.End(xlToRight)).Select

End Sub

Выделить влево до первой или последней заполненной ячейки (равносильно нажатию Ctrl+Shift+Left)

Sub CtrlShiftLeft()

Range(ActiveCell, ActiveCell.End(xlToLeft)).Select

End Sub

Выделить текущую область (выделяется диапазон неразрывно заполненных ячеек — равносильно нажатию кнопок Ctrl+Shift+*)

Sub CtrlShiftUmn()

ActiveCell.CurrentRegion.Select

End Sub

Выделить активную область (происходит выделение всего заполненного диапазона Ctrl+Shift+Home, End, Home)

Sub CtrlShiftHome()

Range(Range(«A1»), ActiveCell.SpecialCells(xlLastCell)).Select

End Sub

Выделить смежные (заполненные прилегающие к активной ячейке) ячейки в столбце с активной ячейкой

Sub SelectActiveColumn()

Dim TopCell As Range

Dim BottomCell As Range

If IsEmpty(ActiveCell) Then Exit Sub

On Error Resume Next

If IsEmpty(ActiveCell.Offset(-1, 0)) Then Set TopCell = _

ActiveCell Else Set TopCell = ActiveCell.End(xlUp)

If IsEmpty(ActiveCell.Offset(1, 0)) Then Set BottomCell = _

ActiveCell Else Set BottomCell = ActiveCell.End(xlDown)

Range(TopCell, BottomCell).Select

End Sub

Выделить смежные ячейки в строке с активной ячейкой

Sub SelectActiveRow()

Dim LeftCell As Range

Dim RightCell As Range

If IsEmpty(ActiveCell) Then Exit Sub

On Error Resume Next

If IsEmpty(ActiveCell.Offset(0, -1)) Then Set LeftCell = _

ActiveCell Else Set LeftCell = ActiveCell.End(xlToLeft)

If IsEmpty(ActiveCell.Offset(0, 1)) Then Set RightCell = _

ActiveCell Else Set RightCell = ActiveCell.End(xlToRight)

Range(LeftCell, RightCell).Select

End Sub

Выделить весь активный столбец

Sub SelectionEntireColumn()

Selection.EntireColumn.Select

End Sub

Выделить всю активную строку

Sub SelectEntireRow()

Selection.EntireRow.Select

End Sub

Выделить рабочий лист

Sub SelectEntireSheet()

Cells.Select

End Sub

Выделить следующую пустую ячейку снизу

Sub CellNextDown()

ActiveCell.Offset(1, 0).Select

Do While Not IsEmpty(ActiveCell)

ActiveCell.Offset(1, 0).Select

Loop

End Sub

Выделить следующую пустую ячейку справа

Sub CellNextRight()

ActiveCell.Offset(0, 1).Select

Do While Not IsEmpty(ActiveCell)

ActiveCell.Offset(0, 1).Select

Loop

End Sub

Выделение от первой непустой до последней непустой ячеек в строке

Sub SelectFirstToLastInRow()

Dim LeftCell As Range

Dim RightCell As Range

Set LeftCell = Cells(ActiveCell.Row, 1)

Set RightCell = Cells(ActiveCell.Row, 256)

If IsEmpty(LeftCell) Then Set LeftCell = LeftCell.End(xlToRight)

If IsEmpty(RightCell) Then Set RightCell = RightCell.End(xlToLeft)

If LeftCell.Column = 256 And RightCell.Column = 1 Then ActiveCell. _

Select Else Range(LeftCell, RightCell).Select

End Sub

Выделение от первой непустой до последней непустой ячеек в столбце

Sub SelectFirstToLastInColumn()

Dim TopCell As Range

Dim BottomCell As Range

Set TopCell = Cells(1, ActiveCell.Column)

Set BottomCell = Cells(16384, ActiveCell.Column)

If IsEmpty(TopCell) Then Set TopCell = TopCell.End(xlDown)

If IsEmpty(BottomCell) Then Set BottomCell = BottomCell.End(xlUp)

If TopCell.Row = 16384 And BottomCell.Row = 1 Then ActiveCell. _

Select Else Range(TopCell, BottomCell).Select

End Sub

Относительные формулы

Некоторые наверно обращали внимание на то, что при записи макроса вводимые формулы имеют непонятый вид. Тут хотелось бы развеять все непонятки и показать, что в этих формулах нет ничего сложного, и в них всё логично, хоть они и выглядят по другому. Рассмотрим простую формулу сложения двух ячеек.

Sub Primer()

Cells(4, 5) = «=RC[-3]+RC[-1]»

Cells(5, 5) = «=R[-3]C+R[-1]C»

End Sub

R — это строка, от слова Row;
C — это столбец, от слова Column;

В квадратных скобках указано на какое количество столбцов или строк необходимо переместится от заданной ячейки. Принцип отчёта такой же как и в команде рассмотренной самой первой в этом уроке — Offset. И как видно на примере, значения в квадратных скобках могут быть как отрицательные так и положительные.

В первом примере показано, что сумма в ячейке Cells(4, 5) равна сумме двух ячеек, одна из которых находится на третьем столбце влево от заданной ячейки, а другая на один столбец влево от заданной ячейки, при этом строка остаётся неизменной. На втором примере всё аналогично, только тут уже столбец остаётся неизменным а меняются строки.

Столбцы и строки

В этом пункте я расскажу как правильно работать со строками и столбцами. Каким образом можно выделить одну строку, две или несколько строк, и тоже самое со столбцами.

Для того чтобы оперировть какой-то одной строкой или столбцом, можно воспользоваться следующей записью:

Rows(6).Select ‘Строка

Columns(6).Select ‘Столбец

Это самая простая запись не требующая никаких кавычек, Вы просто указываете номер строки или столбца и дальше делаете с ней что угодно, например закрасить в синий цвет:

Rows(6).Interior.ColorIndex = 5 ‘Строка

Columns(6).Interior.ColorIndex = 5 ‘Столбец

Если Вам необходимо выделить более одной строки или столбца, то можно воспользоваться следующей записью:

Rows(«2:2»).Select ‘Строка

Columns(«B:B»).Select ‘Столбец

Такая запись выделяет только одну строку и только один столбец.
А вот если записать так:

Rows(«2:5»).Select ‘Строка

Columns(«B:F»).Select ‘Столбец

то мы сможем обработать целый диапазон строк и столбцов, но он сплошной. Если мы хотим обработать разные строки, например 1, 4, 6-8, и разные столбцы, например B, D, F-G, то запись необходимо произвести следующим образом:

Range(«1:1,4:4,6:8»).Select ‘Строка

Range(«B:B,D:D,F:G»).Select ‘Столбец

При такой записи у нас получается уже диапазон. И умногих возник вопрос: «Каким образом можно одновременно выделить и строки и столбцы?». Пример ниже выделяет одновременно 8 строчку и столбец D:

Range(«D:D,8:8»).Select

А этот пример выделяет сразу несколько строк и столбцов одновременно:

Range(«B:B,D:D,3:6,9:9»).Select

Error. Обработка ошибок

Ну вот и пришёл тот долгожданный день, когда Вы задумались об обработке ошибок. Хорошо когда в программах работаете только Вы, и Вы знаете куда в ней можно нажать, а куда не стоит потому что случится ошибка. Но если Вы пишите программу, в которой работает несколько человек помимо Вас и они понятия не имеют что в ней можно и что нельзя, и они обязательно будут тыкать туда куда нельзя, и эти ошибки Вас просто погубят, потому что все будут грешить на вашу программу.
Конечно же в любой программе необходимо предусмотреть «защиту от дурака», но иногда «дурак» превосходит все ожидания. Если честно, я редко пользуюсь командами для обработки ошибок. Стараюсь предусматривать все возможные варианты действий пользователя, но это иногда заставляет написать такую огромную защиту, что сама программа того не стоит. Вообщем приступим.

Искусственно создадим ошибку. Самый простой способ — взять любое число и поделить его на ноль. Напишем простенькую программу и на ней будем рассматривать различные способы обхода ошибки.

Sub primer1()

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

End Sub

Способ №1. Перенаправление программы

Это пожалуй самый популярный способ, так как при возникновении ошибки лучше указать пользователю где он ошибся и перенаправить на другой путь выполнения программы. Сделать перенаправление можно при помощи команды GoTo, но сначала нам надо дать понять программе, что мы хотим отследить ошибку — т.е. включить распознование ошибок. Делается это следующим образом. В начало кода программы мы помещаем следующую команду

On Error

которая и говорит о том, что необходимо включить отслеживание ошибок. А команда GoTo уже перенаправит программу на то место, которое мы укажем. На примере нашей программе это можно продемонстрировать так:

Sub primer1()

On Error GoTo errors

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

GoTo Endprimer

errors:

MsgBox «Ошибка! Вводите корректные данные», vbCritical, «Ошибка»

Endprimer:

End Sub

Поясню. При возникновении ошибки, программа с места ошибки сразу перенаправится на errors: и уже будет продолжать выполнять код с этого места. То-есть с информационного сообщения о том, что у нас возникла ошибка, после этого сообщения программа так и продолжит выполнять макрос дальше, в данном случае программа просто закончится. В случае, если ошибки нет, нам покажется результат и код перенаправится на Endprimer:, так как туда нас перенаправил GoTo.
Если Вы не хотите чтобы макрос выполнялся дальше, то можно добавить команду для выхода из макросаExit Sub (завершение выполнения текущего макроса).

Sub primer1()

On Error GoTo errors

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

GoTo Endprimer

errors:

MsgBox «Ошибка! Вводите кооректные данные», vbCritical, «Ошибка»

Exit Sub

Endprimer:

End Sub

Способ №2. Настойчивость в исправлении ошибки

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

Sub primer2()

On Error GoTo errors

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

GoTo vpered

errors:

MsgBox «Ошибка! Повторите ввод», vbCritical, «Ошибка»

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

Resume

vpered:

End Sub

Тут у нас добавилась только одна новая команда Resume, которая и будет перенапрявлять программу на место возникновения ошибки, тем самым требовать от нас правильного ввода данных, и пока мы их не введём, программа так и будет требовать от нас непонятного.

Способ №3. Предупреждён. Сообщение об ошибке

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

Sub primer3()

On Error GoTo errors

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

GoTo propusk

errors:

If Err.Number 0 Then

resultat = MsgBox(«При выполнении программы произошла ошибка №» & Err.Number & _

vbNewLine & «Продолжить выполнение программы не смотря на ошибку?», _

vbYesNo + vbCritical, «Ошибка»)

If resultat = vbYes Then

Resume Next

Else

Exit Sub

End If

End If

propusk:

End Sub

Тут у нас добавилось ещё одна новая команда Resume Next, которая говорит о том, что код необходимо продолжать выполнять дальше с места обнаружения ошибки, не смотря ни на что.

Способ №4. Очистка ошибки

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

Sub primer4()

On Error Resume Next

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

If Err.Number 0 Then

p = Err.Number

Err.Clear

MsgBox «Ошибка » & p & » очищена!», vbInformation, «Уведомление»

End If

End Sub

Способ №5. Идём напролом

Если Вам наплевать на все ошибки и Вы даже не хотите о них слышать, то можно воспользоваться следующей записью.

Sub primer5()

On Error Resume Next

a = 10

b = InputBox(«Введите число отличное от 0», «Ввод данных», «0»)

c = a / b

MsgBox «Результат: » & a & «/» & b & «=» & c, vbInformation, «Ответ»

End Sub

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

Я думаю, что на основе этих примеров и уловок остальные способы обработки ошибок Вы сможете придумать сами, придумать такие, на какие Вам хватит фантазии.

Понравилась статья? Поделить с друзьями:
  • Excel медленно открывает файлы
  • Excel медиана вариационного ряда
  • Excel мегабайт в гигабайт
  • Excel мгновенное заполнение ячеек
  • Excel матрица по двум строкам