Давайте рассмотрим пример из практики. Он иллюстрирует некоторые концепции, рассмотренные в подборке канала "Процедуры и функции" и "Основы".
В данной статье описана разработка возможно полезной для вас утилиты. Что более важно, ниже показан процесс анализа задачи и последующего ее решения с помощью VBA.
Предупреждение опытным пользователям, читающим статью: учтите, что пример рассчитан на начинающих. Поэтому не только представлен код, но и рассказано, какими источниками можно пользоваться при разработке программы.
Цель проекта
Разработать утилиту, которая изменяет порядок следования листов рабочей книги, сортируя их названия по алфавиту (с помощью одних только функций Excel это сделать невозможно). Если вы часто создаете книги с большим количеством листов, то знаете, что иногда сложно найти интересующий вас лист. Если же их упорядочить по названиям, то найти любой рабочий лист будет значительно проще.
Требования к проекту
С чего начнем? Давайте с перечисления требований к приложению. В процессе разработки я и вы будем обращаться к этому перечню для проверки правильности выполнения действий.
Требования к разрабатываемому приложению приведены ниже:
- Приложение должно сортировать листы (т.е. рабочие листы и листы диаграмм) активной книги по названиям в алфавитном порядке.
- Приложение должно легко выполняться.
- Приложение всегда должно быть доступным. Другими словами, пользователь должен открывать рабочую книгу для использования этой утилиты.
- Приложение должно правильно выполняться по отношению к любой открытой рабочей книге.
- В приложении должна выполняться корректная обработка ошибок, исключающая появление сообщений об ошибках VBA.
Что необходимо знать
Часто самой сложной частью проекта является определение того, с чего же начать. В данном случае попробуем с перечисления особенностей Excel, которые могут повлиять на соблюдение требований к проекту:
- В Excel отсутствует команда сортировки листов, поэтому приложение придаст Excel новые возможности.
- Требуемый макрос нельзя создать путем записи действий пользователя в автоматическом режиме. Но…, подобный автоматически записанный макрос может принести определенную пользу.
- Лист можно легко переместить, перетащив его за ярлычок.
- Для начала: включите функцию записи макросов и перетащите лист в другое место, чтобы узнать, какой код создается при таком действии.
- В Excel можно открыть диалоговое окно Переместить или скопировать, щелкнув правой кнопкой мыши на ярлычке листа с последующим выбором команды контекстного меню Переместить/скопировать. Будет ли при записи макроса для этой команды генерироваться код, который работает иначе, чем операция перемещения листа вручную?
- Скорее всего нам следует знать, сколько листов содержится в активной рабочей книге. Эту информацию можно получить с помощью VBA.
- Узнать названия листов (вновь воспользовавшись VBA).
- В Excel существует команда, сортирующая данные в ячейках рабочего листа.
- Здесь для нас выбор: возможно, стоит перенести названия листов в диапазон ячеек и использовать этот вариант. Или, возможно, в VBA есть метод сортировки, которым можно будет воспользоваться в программе.
- Нам понадобится тестировать приложение по мере разработки. Естественно, нельзя тестировать приложение в той же рабочей книге, в которой оно разработано. Это означает, что макрос должен быть сохранен в личной книге макросов.
- Итак: создайте фиктивную рабочую книгу, предназначенную специально для тестирования.
Применяемый подход
На данном этапе мы точно не знаем, что будем делать дальше, однако можно набросать предварительный план, описывающий общие задачи. Необходимо определить, как выполнить следующие операции:
- идентифицировать активную рабочую книгу.
- получить список названий листов.
- сосчитать листы активной рабочей книги.
- отсортировать список.
- изменить порядок следования листов в соответствии с отсортированным списком.
И давайте не будем бояться, если вы пока не знаете, как написать соответствующий код. Скорее всего многие разработчики не представляют заранее, как должна выглядеть программа и какой синтаксис следует применять. Всю необходимую информацию можно получить с помощью средства записи макросов, справочной системы VBA и примеров в Интернете.
Предварительная запись действий
Лучший способ приступить к написанию процедуры VBA - воспользоваться средством создания макросов (макрорекодером). Дана книга с тремя рабочими листами. Для начала нам нужно узнать код VBA для перемещения листов.
Включим средство записи макросов и укажем, что макрос должен сохраняться в личной книге макросов (это позволит тестировать код на других книгах, отличных от текущей). В режиме записи перетащим третий рабочий лист перед первым листом.
Остановите запись кода и посмотрите на полученный результат. Как видите (в моём случае), в сгенерированный код включен метод Move.
В справочной системе можно узнать, что метод Move перемещает лист в рабочей книге на новое место. Данный метод имеет один аргумент, определяющий будущее положение листа. За определение положения листа отвечает код Before: =Sheets (1).
Вам также необходимо узнать количество листов в активной рабочей книге. Запросив сведения о слове Count, узнаем, что это свойство коллекции. А это означает, что все коллекции, такие как Sheets, Rows, Cells и Shapes, включают свойство Count. Весьма ценная информация?!
Чтобы протестировать введенный фрагмент кода, откройте окно VBE, отобразите окно отладки (Immediate) и введите следующий код:
? ActiveWorkbook.Sheets.Count
А что с названиями листов? Проведем еще один тест. Введем в окне отладки следующий оператор:
? ActiveWorkbook.Sheets(1).Name
В результате будет получено название первого листа – Лист3 (так и есть на самом деле ведь теперь он первый). Таким образом, мы получили положительный результат.
Далее конструкция For Next, рассмотренная в статье 53, используется для циклического просмотра всех членов коллекции.
И вновь успех! Макрос поочерёдно отобразил три окна сообщения, в каждом из которых новое название листа.
Теперь, пришло время подумать о функциях сортировки. Справочная система подсказывает, что метод Sort относится к объекту Range. Здесь два пути: одним из решений задачи будет перенесение названия листов в диапазон ячеек и сортировка этого диапазона. Однако такая задача выглядит довольно сложной для данного приложения. Возможно, целесообразнее сформировать из названий листов массив строк, а затем отсортировать этот массив с использованием кода VBA.
📌 Скачать файл пример, можно в конце статьи.
Подготовка
Теперь мы обладаем достаточным объемом информации для того, чтобы приступить к работе над программой. Однако прежде следует задать первоначальные настройки. Итак, выполним следующие действия.
1. Создайте пустую рабочую книгу с пятью рабочими листами: Лист1, Лист2, Лист3, Лист4 и Лист5.
2. Разместите листы произвольно, чтобы они следовали не по порядку.
3. Сохраните рабочую книгу как Test.xlsm.
4. Активизируйте VBE и выберите проект Personal.xlsb в окне Project (Проект). Если Personal.xlsb не отображается в окне Project, значит, вы никогда не использовали личную книгу макросов. Еxcel создаст для вас эту книгу, когда вы запишете макрос (любой) и определите, что он должен сохраняться в личной книге макросов.
5. Добавьте новый модуль VBA в книгу Personal.xlsb, используя команду Insert=>Module (Вставить=>Модуль).
6. Создайте пустую процедуру с названием SortSheets (смотри скриншот).
7. Активизируйте Excel. Выберите команду Разработчик=>Код=>Макросы для отображения диалогового окна Макрос.
8. В диалоговом окне Макрос выберите процедуру SortSheets и щелкните на кнопке Параметры, чтобы присвоить данному макросу СВОЮ комбинацию клавиш. Например <Ctrl+Shift+S>.
Пишем код
Теперь пришло время приступить к написанию программы. Вначале необходимо поместить названия листов в массив строк. Так как пока неизвестно, сколько листов содержит активная рабочая книга, для объявления массива используем инструкцию Dim с пустыми скобками. Знайте, что всегда можно применить инструкцию ReDim и изменить размерность массива на требуемое число элементов.
Добавим код, включающий названия листов в массив SheetNames. Кроме того, в цикл добавим функцию MsgBox, чтобы убедиться, что названия листов на самом деле вводятся в массив.
Чтобы проверить эту программу, активизируйте рабочую книгу Test.xlsm и нажмите комбинацию клавиш <Ctrl+Shift+S>. Появится пять окон сообщений с названиями листов активной рабочей книги. Имейте ввиду, что если раскладка клавиатуры будет русская, то при комбинации клавиш <Ctrl+Shift+S> макрос не активируется. Имеет смысл «посадить» макрос на клавиши удобные для вас.
Рекомендуется тестировать код по мере его создания. Когда вы убедитесь, что программа работает правильно, удалите инструкции MsgBox (через некоторое время они начнут вас раздражать).
Вместо того чтобы использовать функцию MsgBoх в целях тестирования, можно обратиться к методу Print объекта Debug, который отображает сведения в окне отладки. Для этого замените строку MsgBox следующей инструкцией:
Debug.Print SheetNames(i)
Этот прием не столь навязчив по сравнению с использованием функций MsgBox. Не забудьте только удалить данную строку по завершении тестирования.
На данном этапе процедура SortSheets всего лишь создает массив названий листов в соответствии с порядком их указания в активной рабочей книге. Остается два шага: отсортировать значения в массиве SheetNames и изменить порядок следования листов в книге согласно отсортированному массиву.
Создаём процедуру сортировки
Переходим к сортировке массива SheetNames. Можно вставить код сортировки в процедуру SortSheets, но лучше написать общую процедуру сортировки, которую можно будет использовать для управления другими объектами (сортировка массивов - довольно популярная операция). Возможно, вас несколько обескуражит идея создания процедуры сортировки. Однако не беспокойтесь: несложно найти стандартные процедуры, которые можно непосредственно или с небольшими изменениями использовать в своей программе. Конечно, наиболее полным источником такой информации является Интернет.
Существует несколько способов сортировки массивов. Давайте выберем пузырьковый метод (хотя это не очень быстрый прием, его легко запрограммировать). В данном конкретном приложении высокая скорость выполнения операций не так уж важна.
В пузырьковом методе используется вложенный цикл For Next, в котором оценивается каждый элемент массива. Если элемент массива больше, чем следующий, то эти два элемента меняются местами. Такое сравнение повторяется для каждой пары элементов (т.е. n - 1 раз).
В статьях 130, 131, 132, 133 описаны пузырьковый и другие процедуры сортировки, а также приведено сравнение процедур по скорости выполнения.
Ниже приведена процедура сортировки.
Эта процедура принимает один аргумент: одномерный массив с названием List. Массив, который передается процедуре, может быть любой длины. Для присвоения нижней и верхней границ массива переменным First и Last использовались функции Lbound и UBound соответственно.
Временная процедура
Для большей ясности давайте создадим временную процедуру (сортировка имён), которая используется для тестирования процедуры BubbleSort.
Процедура SortName создает массив из шести строк, передает его процедуре BubbleSort и отображает отсортированный массив в окне отладки (смотри скриншот выше). После того как код (сортировки имён) выполнил свое предназначение, давайте его удалим.
Убедившись в том, что код работает, изменим процедуру SortSheets путем добавления вызова процедуры BubbleSort и передачи массива SheetNames в качестве аргумента. Начиная с этого момента модуль приобретает следующий вид.
По окончании работы процедуры SortSheets образуется массив, состоящий из отсортированных названий листов активной рабочей книги. Чтобы проверить это, можно отобразить содержимое массива в окне отладки, добавив в конец процедуры SortSheets такой код (если это окно не отображается, нажмите комбинацию клавиш <Ctrl+G>):
Пока полёт нормальный. Осталось написать программу для изменения порядка следования листов в книге в соответствии с отсортированными элементами массива SheetNames.
Программа, записанная ранее, вновь пригодилась. Помните инструкцию, которая была получена при перемещении листа в рабочей книге в первую позицию?
Sheets("Лист3").Move Before:=Sheets(1)
Далее напишем цикл For Next, который просматривает каждый лист и перемещает его в соответствующее место, указанное в массиве SheetNames.
Например, на первой итерации цикла счетчик і равен 1. Первый элемент массива SheetNames (в данном примере) - лист Лист1. Следовательно, выражение для метода Move в цикле будет таким:
Sheets("Лист1").Move Before:= Sheets(1)
После выполнения второй итерации цикла выражение имеет следующий вид:
Sheets("Лист2").Move Before:=Sheets(2)
В процедуру SortSheets добавим новый код показанный на скриншоте выше.
Протестировал.
Показало, что процедура хорошо работает с рабочей книгой Test.xlsm.
Теперь необходимо собрать весь код воедино. Объявим все переменные, используемые в процедурах, и добавим несколько комментариев, а также пустых строк, чтобы программу можно было легче прочесть. В результате процедура SortSheets примет такой вид.
Итак, вроде все работает. Чтобы продолжить проверку программы, добавим еще несколько листов в книгу Test.xlsm и изменим некоторые названия. Программа работает прекрасно! Вроде…
Дополнительное тестирование
Теперь можно посчитать, что работа окончена. Однако тот факт, что процедура справляется с рабочей книгой Test.xlsm, не означает, что она будет работать со всеми рабочими книгами. Чтобы проверить программу, загрузим несколько разнообразных других рабочих книг и вновь запустим программу. Скоро вы убедитесь в том, что приложение неидеально (если быть точным, оно далеко от идеала).
Итак, были обнаружены следующие проблемы.
- Рабочие книги с большим количеством листов сортируются довольно долго, так как при операциях перемещения окно постоянно обновляется.
- Если на экране не отображаются окна рабочих книг, то при нажатии комбинации клавиш <Ctrl+Shift+S> макрос выдает ошибку.
- Если структура рабочей книги защищена, то метод Move не работает.
- После сортировки последний лист рабочей книги становится активным. Изменение активного листа не совсем удачное решение проблемы; лучше, если бы активным оставался лист, который был таковым до начала выполнения программы.
- При прерывании выполнения макроса с помощью комбинации клавиш <Ctrl+Break> VBA отображает сообщение об ошибке.
- Макрос не может быть "обращен вспять" (вы не можете воспользоваться командой Отменить). Если пользователь случайно нажмет комбинацию клавиш <Ctrl+Shift+S>, листы рабочей книги отсортируются, и придется вручную возвращать их в исходное состояние.
Устранение проблем
Решить проблему обновления изображения на экране несложно для этого вставьте в начале процедуры SortSheets такую строку:
Application.ScreenUpdating = False
Этот оператор как бы "замораживает" окна Excel на время выполнения макроса. Еще один бонус заключается в том, что увеличивается скорость работы макроса. Когда макрос завершается, снова включим автоматическое обновление экрана.
Application.ScreenUpdating = True
Чтобы избежать сообщения об ошибке, которое появляется, когда все рабочие книги свернуты, добавим процедуру проверки ошибок. Если не существует активных рабочих книг, возникает ошибка. Можно применить директиву On Error Resume Next, чтобы проигнорировать ошибку, и проверить значение Err. Если Err не равно нулю, это означает, что произошла ошибка. Следовательно, процедура заканчивается. Ниже приведен код проверки ошибок.
On Error Resume Next
SheetCount = ActiveWorkbook.Sheets.Count
If Err <> 0 Then Exit Sub ' нет активной рабочей книги
Кстати можно и не использовать директиву On Error Resume Next. Следующая инструкция непосредственно определяет, свернута ли рабочая книга, и не реализует обработку ошибок. Эта инструкция будет находиться в верхней части процедуры SortSheets.
If ActiveWorkbook Is Nothing Then Exit Sub
Обычно для защиты структуры рабочей книги имеется веская причина. Предположим, что мы не будем снимать защиту; программа должна отображать предупреждение, чтобы пользователь сам снял защиту и снова выполнил макрос. Проверку защищенной структуры книги выполнить несложно: свойство ProtectStructure объекта WorkBook возвращает True, если книга защищена. Поэтому добавим в проект следующий код.
Проверка защищенной структуры рабочей книги
Для повторной активизации листа после завершения сортировки нужен код, который сопоставляет исходный лист с объектной переменной (OldActiveSheet), а также активизирует этот лист после завершения процедуры. Ниже показан оператор, который инициализирует переменную.
Set OldActive = ActiveSheet
А следующий оператор активизирует рабочий лист, который был изначально активным:
OldActive.Activate
После нажатия комбинации клавиш <Ctrl+Break> выполнение макроса обычно приостанавливается, и VBA выдает сообщение об ошибке. Но так как одна из целей проекта - избежать сообщений об ошибке, необходимо вставить команду предотвращения подобной ситуации. В справочной системе указано, что объект Application обладает свойством EnableCancelKey, которое может отключить комбинацию клавиш <Ctrl+Break>. Поэтому добавим следующий оператор в начало программы:
Application.EnableCancelKey = xlDisabled
Внимание! Здесь следует быть осторожным! Когда отключаете прерывание макроса, выполняемое с помощью клавиш <Ctrl+Break>. Если программа попадет в бесконечный цикл, выйти из него вы не сможете. Лучше использовать этот оператор, когда все работает идеально.
Для предотвращения проблемы, возникающей из-за случайной сортировки листов, перед отключением комбинации клавиш <Ctrl+Break> в процедуру добавим следующий оператор.
После выполнения всех описанных выше изменений процедура SortSheets принимает следующий вид:
Доступность
Макрос SortSheets сохранен в личной книге макросов, поэтому всегда доступен при запуске Excel. На текущем этапе макрос может выполняться при выборе названия макроса в диалоговом окне Макрос - это окно отображается после нажатия комбинации клавиш <Alt+F8> или <Ctrl+Shift+S>. Команду вызова этого макроса можно также добавить на ленту.
Для добавления команды на ленту выполните следующие действия.
1. Щелкните правой кнопкой мыши на ленте и в контекстном меню выберите команду Настройка ленты.
2. На вкладке Настроить ленту диалогового окна Параметры Excel в списке Выбрать команды выберите категорию Макросы.
3. Щелкните на значке PERSONAL.XLSB! Sportsheets.
4. Используйте элементы управления в правом окне для создания новой вкладки и группы ленты. (Вы не сможете добавить команду в существующую группу.)
Мы создали настраиваемую группу на вкладке Вид и переименовали новый, добавленный в эту группу, элемент в Сортировка листов (см. скриншот).
Оценим работу
Итак, результат получен. Утилита соответствует всем изначальным требованиям: сортирует все листы в активной рабочей книге, ее можно легко выполнить, она всегда доступна, выполняется (что легко проверить) для всех рабочих книг и пока еще не отображала сообщений об ошибке VBA.
Но!!! В процедуре все еще имеется одна проблема: сортировка достаточно строгая и не всегда кажется логичной. Например, после сортировки лист Лист10 располагается перед листом Лист2. Большинство пользователей предпочитают видеть лист Лист2 перед листом Лист10. Решить указанную проблему довольно сложно, и это выходит на данный момент за рамки примеров, рассматриваемых в данной статье.