?изнес-решения Применение VBA и макросов в Microsoft® Excel ?usiness solutions VBA and Macros for Microsoft® Excel Bill Jelen, Mr. Excel Tracy Syrstad 800 East 96th Street Indianapolis, Indiana 46240 ?изнес-решения Применение VBA и макросов в Microsoft® Excel Билл Джелен, “Мистер Excel” Трейси Сирстад Москва • Санкт-Петербург • Киев 2006 ББК 32.973.26018.2.75 Д40 УДК 681.3.07 Издательский дом ‘‘Вильямс” Главный редактор С.Н. Тригуб Зав. редакцией В.Р. Гинзбург Перевод с английского и редакция А.В. Журавлева По общим вопросам обращайтесь в Издательский дом ‘‘Вильямс’’ по адресу: [email protected], http://www.williamspublishing.com 115419, Москва, а/я 783; 03150, Киев, а/я 152 Джелен, Билл, Сирстад, Трейси. Д40 Применение VBA и макросов в Microsoft Excel. : Пер. с англ. М. : Из дательский дом ‘‘Вильямс’’, 2006. 624 с. : ил. Парал. тит. англ. ISBN 5845908825 (рус.) В этой книге рассматривается автоматизация выполнения всевозможных задач с помощью Excel VBA от создания простого отчета до разработки полноценного приложения Excel ‘‘с нуля’’. Авторы книги полагаются на достаточно высокий уро вень подготовки читателя, однако допускают, что материал каждой главы не зна ком ему в полном объеме. Особое внимание при изложении материала уделяется таким высокоэффективным средствам Excel, как диаграмма, расширенный фильтр и сводная таблица. Прежде чем продемонстрировать решение той или иной задачи с помощью VBA, авторы кратко останавливаются на ее выполнении с помощью пользовательского интерфейса Excel. Прочитав книгу, читатель получит знания, необходимые для автоматизации выполнения повседневных задач и создания соб ственных решений в Excel с помощью VBA. Книга предназначена для опытных пользователей Excel. ББК 32.973.26018.2.75 Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механиче ские, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Que Corporation. Authorized translation from the English language edition published by Sams Publishing, Copyright © 2004. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage re trieval system, without permission from the publisher. Russian language edition is published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2006. ISBN 5845908825 (рус.) ISBN 0789731290 (англ.) © Издательский дом ‘‘Вильямс’’, 2006 © Sams Publishing, 2004 Îãëàâëåíèå Об авторах Посвящения Благодарности Введение 23 23 23 25 Часть I. Первые шаги 35 Глава 1. Excel и VBA — гремучая смесь Глава 2. Знакомство с Visual Basic for Applications Глава 3. Работа с диапазоном ячеек Глава 4. Функции, определенные пользователем Глава 5. Циклы и управление выполнением кода Глава 6. Стиль записи ссылок R1C1 Глава 7. Имена Глава 8. События Глава 9. Введение в пользовательские формы 37 59 95 111 141 161 177 189 215 Часть II. Автоматизация Excel 227 Глава 10. Диаграммы Глава 11. Анализ данных с помощью расширенного фильтра Глава 12. Сводные таблицы Глава 13. Excel всемогущий Глава 14. Взаимодействие с Internet Глава 15. Поддержка XML в профессиональном выпуске Excel 2003 Глава 16. Автоматизация Word 229 267 299 363 407 427 439 Часть III. Удивительные возможности Visual Basic for Applications Глава 17. Массивы Глава 18. Работа с текстовыми файлами Глава 19. Использование Microsoft Access Глава 20. Создание пользовательских объектов, типов и коллекций Глава 21. Пользовательские формы — профессиональный подход Глава 22. Интерфейс прикладного программирования (API) Windows 461 463 473 489 505 525 547 6 Оглавление Глава 23. Обработка ошибок Глава 24. Создание пользовательских меню и панелей инструментов Глава 25. Надстройки Глава 26. Практикум: создание приложения Excel “с нуля” Предметный указатель 561 575 593 603 615 Ñîäåðæàíèå Об авторах Посвящения Благодарности Введение VBA — работа на результат Как организована эта книга Часть I, “Первые шаги” Часть II, “Автоматизация Excel” Часть III, “Удивительные возможности Visual Basic for Applications” Для кого предназначена эта книга История развития электронных таблиц и макросов Будущее Excel и VBA Соглашения, принятые в этой книге Рассматриваемые версии Excel Программный код Следующий шаг Ждем ваших отзывов! Часть I. Первые шаги Глава 1. Excel и VBA — гремучая смесь Excel всемогущий Камни преткновения Средство записи макросов не работает! Visual Basic — это не BASIC Хорошие новости Отличные новости Панель инструментов “Visual Basic” Безопасность макросов Уровень безопасности “Очень высокая” Уровень безопасности “Высокая” Уровень безопасности “Средняя” Уровень безопасности “Низкая” Запись, хранение и выполнение макросов Диалоговое окно “Запись макроса” Выполнение макроса Создание кнопки выполнения макроса Назначение макроса элементу управления формы Редактор Visual Basic Параметры редактора Visual Basic 23 23 23 25 25 27 27 27 27 28 29 30 31 32 32 32 33 35 37 37 37 37 38 38 38 39 40 40 40 41 41 42 42 43 43 44 45 45 8 Содержание Диспетчер проектов Окно свойств Изучение кода макроса Непредвиденные результаты Возможное решение: использование относительных ссылок Отчаяние Следующий шаг Глава 2. Знакомство с Visual Basic for Applications Загадочный код Учимся понимать “речь” VBA Справочная система VBA Спасительная клавиша <F1> Просмотр разделов справочной системы Изучение кода записанного макроса 46 47 50 52 52 57 57 59 59 60 63 63 65 66 Необязательные параметры Предопределенные константы Возврат объектов свойством 68 68 73 Использование отладчика кода 74 Пошаговое выполнение кода Точки прерывания Перемещение по коду Выполнение фрагмента кода Вычисление значения переменной или выражения Установка точки прерывания с помощью окна Watches Отслеживание состояния объекта с помощью окна Watches 74 77 78 79 79 83 83 Диспетчер объектов 5 советов по исправлению и оптимизации автоматически сгенерированного кода 85 Совет 1: ничего не выделяйте Совет 2: перемещайтесь на последнюю строку данных с конца рабочего листа Совет 3: используйте переменные Совет 4: используйте одно выражение для копирования и вставки данных Совет 5: используйте конструкцию With...End With Исправление и оптимизация автоматически сгенерированного кода Следующий шаг Глава 3. Работа с диапазоном ячеек Объект Range Обращение к диапазону ячеек с помощью указания адреса его верхнего левого и нижнего правого угла Сокращенная форма обращения к диапазону ячеек Именованные диапазоны ячеек 87 87 88 89 89 90 90 93 95 95 96 96 96 Содержание Обращение к диапазону ячеек, расположенному на другом рабочем листе Обращение к диапазону ячеек с помощью указания его относительного адреса Обращение к диапазону ячеек с помощью свойства Cells Использование свойства Cells в качестве параметра свойства Range Обращение к диапазону ячеек с помощью свойства Offset Изменение размера диапазона ячеек с помощью свойства Resize Обращение к диапазону ячеек с помощью свойств Columns и Rows Объединение диапазонов ячеек с помощью метода Union Создание нового диапазона ячеек из пересекающихся диапазонов с помощью метода Intersect Проверка пустых ячеек с помощью функции IsEmpty Обращение к диапазону ячеек с помощью свойства CurrentRegion Обращение к диапазону несмежных ячеек с помощью коллекции Areas Следующий шаг 97 98 99 100 100 101 102 103 103 104 105 108 109 Глава 4. Функции, определенные пользователем Создание функций, определенных пользователем Наиболее распространенные задачи программирования в Excel 111 111 113 Вывод имени файла текущей рабочей книги в ячейке Вывод полного имени файла текущей рабочей книги в ячейке Как проверить, открыта ли рабочая книга Проверка существования рабочего листа в открытой книге Подсчет количества файлов рабочих книг в папке Получение имени пользователя, зарегистрировавшегося в системе Получение даты и времени последнего сохранения рабочей книги Получение постоянного значения даты и времени Проверка адреса электронной почты Суммирование значений ячеек на основе цвета заливки Получение имени и номера цвета заливки ячейки Получение номера цвета текста в ячейке Подсчет количества уникальных значений Удаление повторяющихся значений из диапазона ячеек Поиск первой непустой ячейки в диапазоне Замена нескольких символов в строке Извлечение чисел из смешанного текста Преобразование номера недели в дату Разбор строки с символамиYразделителями Сортировка и конкатенация значений ячеек из заданного диапазона Сортировка числовых и строковых значений Поиск строки в диапазоне ячеек Запись содержимого ячейки в обратном порядке Поиск наибольших значений в диапазоне ячеек Получение адреса гиперссылки 113 113 114 114 115 116 117 118 118 120 121 124 125 125 128 128 130 130 131 132 134 135 136 137 138 9 10 Содержание Получение адреса столбца ячейки Генерация постоянных случайных чисел Использование структуры Select...Case Следующий шаг Глава 5. Циклы и управление выполнением кода Цикл For...Next Использование переменных в выражении For Изменение шага в цикле For...Next Досрочное завершение выполнения цикла Вложение циклов Циклы Do...Loop Использование операторов While и Until Цикл While...Wend Цикл For Each...Next Объектные переменные Управление выполнением кода: использование конструкций If...Then...Else и Select Case Знакомство с конструкцией If...Then...Else Условие Конструкция If...Then...End If Конструкция If...Then...Else...End If Конструкция If...ElseIf...End If Конструкция Select Case...End Select Использование сложных выражений Case Вложение выражений If Следующий шаг Глава 6. Стиль записи ссылок R1C1 Сравнение стилей записи ссылок A1 и R1C1 R1C1 — дела давно минувших дней? R1C1 — сильные стороны Использование стиля ссылок R1C1 в Excel Чудесный мир формул Excel Как “размножаются” формулы Разоблачение Ссылки в стиле R1C1 Относительные ссылки в стиле R1C1 Абсолютные ссылки в стиле R1C1 Смешанные ссылки в стиле R1C1 Обращение к строке или столбцу с помощью ссылок в стиле R1C1 Замена нескольких A1Yформул одной R1C1Yформулой Тренируем память Использование ссылок в стиле R1C1 при условном форматировании ячеек Задание условного форматирования с помощью пользовательского интерфейса 138 138 139 140 141 141 144 144 145 146 147 150 152 152 152 155 155 156 156 156 157 157 158 158 160 161 161 162 162 162 163 163 164 166 166 167 167 168 169 170 171 171 Содержание Задание условного форматирования с помощью VBA Использование ссылок в стиле R1C1 при создании формулы массива Следующий шаг Глава 7. Имена Глобальные и локальные имена Создание имен Удаление имен Типы имен Имена формул Имена строк Имена чисел Имена массивов Зарезервированные имена Скрытие имен Проверка существования имени Следующий шаг Глава 8. События Использование событий Параметры событий Запрет обработки событий События рабочей книги Событие Workbook_Activate() Событие Workbook_Deactivate() Событие Workbook_Open() Событие Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) Событие Workbook_BeforePrint(Cancel As Boolean) Событие Workbook_BeforeClose(Cancel As Boolean) Событие Workbook_NewSheet(ByVal Sh As Object) Событие Workbook_WindowResize(ByVal Wn As Window) Событие Workbook_WindowActivate(ByVal Wn As Window) Событие Workbook_WindowDeactivate(ByVal Wn As Window) Событие Workbook_AddinInstall() Событие Workbook_AddinUninstall() Событие Workbook_SheetActivate(ByVal Sh As Object) Событие Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие Workbook_SheetCalculate(ByVal Sh As Object) Событие Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range) Событие Workbook_SheetDeactivate(ByVal Sh As Object) 172 174 175 177 177 179 180 180 181 181 182 183 183 185 185 187 189 190 190 191 191 192 192 192 193 193 194 195 195 196 196 196 196 197 197 197 197 198 198 11 12 Содержание Событие Workbook_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As Hyperlink) Событие Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) События рабочего листа Событие Worksheet_Activate() Событие Worksheet_Deactivate() Событие Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean) Событие Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean) Событие Worksheet_Calculate() Событие Worksheet_Change(ByVal Target As Range) Событие Worksheet_SelectionChange(ByVal Target As Range) Событие Worksheet_FollowHyperlink(ByVal Target As Hyperlink) События листа диаграммы Встроенные диаграммы Событие Chart_Activate() Событие Chart_BeforeDoubleClick(ByVal ElementID As Long, ByVal Arg1 As Long, ByVal Arg2 As Long, Cancel As Boolean) Событие Chart_BeforeRightClick(Cancel As Boolean) Событие Chart_Calculate() Событие Chart_Deactivate() Событие Chart_DragOver() Событие Chart_DragPlot() Событие Chart_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_MouseMove(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_MouseUp(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_Resize() Событие Chart_Select(ByVal ElementID As Long, ByVal Arg1 As Long, ByVal Arg2 As Long) Событие Chart_SeriesChange(ByVal SeriesIndex As Long, ByVal PointIndex As Long) События приложения Событие AppEvent_NewWorkbook(ByVal Wb As Workbook) Событие AppEvent_SheetActivate(ByVal Sh As Object) Событие AppEvent_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие AppEvent_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие AppEvent_SheetCalculate(ByVal Sh As Object) Событие AppEvent_SheetChange(ByVal Sh As Object, ByVal Target As Range) Событие AppEvent_SheetDeactivate(ByVal Sh As Object) 198 198 199 199 199 199 200 200 202 203 203 204 204 205 205 206 206 206 206 206 206 207 207 207 207 208 208 210 210 210 210 211 211 211 Содержание Событие AppEvent_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As Hyperlink) Событие AppEvent_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) Событие AppEvent_WindowActivate(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WindowDeactivate(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WindowResize(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WorkbookActivate(ByVal Wb As Workbook) Событие AppEvent_WorkbookAddinInstall(ByVal Wb As Workbook) Событие AppEvent_WorkbookAddinUninstall(ByVal Wb As Workbook) Событие AppEvent_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean) Событие AppEvent_WorkbookBeforePrint(ByVal Wb As Workbook, Cancel As Boolean) Событие AppEvent_WorkbookBeforeSave(ByVal Wb As Workbook, ByVal SaveAsUI As Boolean, Cancel As Boolean) Событие AppEvent_WorkbookDeactivate(ByVal Wb As Workbook) Событие AppEvent_WorkbookNewSheet(ByVal Wb As Workbook, By Val Sh As Object) Событие AppEvent_WorkbookOpen(ByVal Wb As Workbook) Следующий шаг Глава 9. Введение в пользовательские формы Способы взаимодействия с пользователем Окно ввода Окно сообщения Создание пользовательской формы Вызов и скрытие пользовательской формы Программирование пользовательской формы Основные элементы управления формы Использование списков и комбинированных списков Использование переключателей Использование изображений Использование счетчиков Использование вкладок для объединения форм Проверка ввода обязательных данных Закрытие формы Следующий шаг Часть II. Автоматизация Excel Глава 10. Диаграммы Встроенные диаграммы и диаграммы, расположенные на отдельном листе 211 211 211 211 212 212 212 212 213 213 213 213 213 214 214 215 215 215 216 216 218 218 220 221 222 223 224 225 225 225 226 227 229 229 13 14 Содержание Встроенные диаграммы и контейнер ChartObject Диаграммы, расположенные на отдельном листе 230 232 Создание диаграмм с помощью VBA 232 Изменение размещения диаграммы Стандартный тип диаграмм 235 235 Использование объектных переменных для упрощения кода “Анатомия” диаграммы Область диаграммы (ChartArea) Область построения диаграммы (PlotArea) Ряды данных (Series) Оси диаграммы (Axis) Линии сетки (HasMajorGridlines и HasMinorGridlines) Подписи данных (DataLabels и DataLabel) Название диаграммы, легенда и таблица данных (ChartTitle, HasLegend и HasDataTable) Линии тренда и полосы погрешности (Trendlines и ErrorBar) Типы диаграмм Параметры трехмерных и круговых диаграмм Параметры трехмерных диаграмм Параметры круговых диаграмм Интерактивные диаграммы События диаграмм 236 237 237 240 242 243 245 246 247 248 251 256 256 258 260 260 Экспорт диаграммы в файл изображения Удивительные возможности точечных диаграмм Создание нестандартных диаграмм 261 262 262 Круговая пузырьковая диаграмма Диаграмма с точками данных в виде спидометров Диаграмма кривой предложения Иерархическая кольцевая диаграмма 262 264 264 265 Следующий шаг Глава 11. Анализ данных с помощью расширенного фильтра Преимущества VBA перед пользовательским интерфейсом Excel Использование расширенного фильтра для отбора уникальных значений из заданного диапазона Отбор уникальных значений из заданного столбца с помощью пользовательского интерфейса Отбор уникальных значений из заданного столбца с помощью VBA Отбор уникальных значений из комбинации нескольких столбцов с помощью VBA Использование расширенного фильтра с указанием условия отбора данных Объединение нескольких условий с помощью логической операции “ИЛИ” Объединение нескольких условий с помощью логической операции “И” Дополнительные аспекты объединения условий с помощью логической операции “ИЛИ” 266 267 267 268 268 271 275 276 278 278 279 Содержание Задание условия отбора с помощью формулы Отбор пустого множества записей Фильтрация диапазона исходных данных “на месте” Отбор пустого множества записей Отображение записей, скрытых в результате фильтрации “на месте” Отбор только уникальных записей при фильтрации “на месте” Использование расширенного фильтра для копирования всех записей, удовлетворяющих заданному условию Копирование всех столбцов исходного диапазона данных Копирование и переупорядочивание подмножества столбцов исходного диапазона данных Автофильтр Следующий шаг Глава 12. Сводные таблицы Сводные таблицы в различных версиях Excel Создание сводных таблиц с помощью пользовательского интерфейса Excel Создание сводных таблиц с помощью VBA Подсчет суммы чисел вместо количества значений Перемещение или изменение части сводной таблицы Определение размера сводной таблицы Создание отчета о структуре спроса на товары Заполнение значениями пустых ячеек в области данных Изменение порядка сортировки списка заказчиков Изменение порядка следования столбцов сводной таблицы вручную Изменение формата отображения числовых значений Запрет автоматического добавления промежуточных итогов Запрет подсчета общей суммы по столбцам Создание отчета о структуре спроса на товары: завершающая стадия Создание новой рабочей книги Копирование содержимого сводной таблицы Улучшение внешнего вида отчета Стилевое форматирование отчета Добавление промежуточных итогов Результирующий код Создание отчета о прибыльности товаров Определение вычисляемых полей области данных “Подводные камни” вычисляемых элементов Суммирование значений полей области данных сводной таблицы путем группирования Группирование дат по неделям Определение сроков выполнения заказов Дополнительные возможности сводных таблиц Отображение лучшей десятки заказчиков Использование сводной таблицы для фильтрации исходных данных 279 286 287 288 288 288 289 289 290 297 298 299 299 300 303 305 306 307 309 312 313 314 315 315 316 317 318 318 319 321 321 323 326 328 331 332 336 338 340 340 344 15 16 Содержание Использование полей области страницы сводной таблицы Фильтрация элементов полей сводной таблицы вручную Сумма, среднее, количество, минимум, максимум и др. Дополнительные вычисления в полях области данных сводной таблицы Доля от общей суммы Приведенное отличие от значения предыдущего элемента поля Приведенное отличие от значения заданного элемента поля Нарастающий итог Следующий шаг Глава 13. Excel всемогущий Расширение возможностей Excel с помощью VBA Условное форматирование с более чем тремя условиями Расширенный фильтр с более чем двумя условиями Файловые операции Поиск файлов Удаление рабочей книги после определенной даты Создание команды меню “Закрыть и удалить” Импорт CSVYфайлов Считывание текстового файла в память и его последующий анализ Объединение и разделение рабочих книг Сохранение листов рабочей книги в виде отдельных рабочих книг Объединение нескольких рабочих книг в одну Фильтрация данных с последующим копированием полученного результата в отдельные рабочие листы Экспорт данных в Word Работа с примечаниями Вывод примечаний Изменение размера области примечания Изменение размера области примечания с помощью центрирования Размещение диаграммы в примечании Замечательные возможности Excel VBA Выделение ячейки с помощью условного форматирования Выделение ячейки без применения условного форматирования Транспонирование данных Выделение и отмена выделения несмежных ячеек VBA для профессионалов Установка параметров страницы Вычисление времени выполнения кода макроса Запрет/разрешение выполнения операций вырезания, копирования и вставки Определение порядка сортировки Создание индикатора хода процесса Создание защищенного поля для ввода пароля Изменение регистра текста Обработка события удаления строки или столбца Поиск заданного текста с помощью свойства SpecialCells 346 351 355 356 358 358 359 359 361 363 363 364 364 365 366 367 368 370 370 372 372 372 373 375 376 376 377 378 379 381 381 382 383 384 386 386 389 390 392 394 395 397 398 399 Содержание Условное удаление строк Сокрытие строки формул На закуску Извлечение информации о курсах акций из Internet Вставка программного кода во вновь созданную рабочую книгу Следующий шаг Глава 14. Взаимодействие с Internet Извлечение данных из Internet Создание WebYзапроса с помощью пользовательского интерфейса Excel Обновление существующего WebYзапроса с помощью VBA Создание WebYзапроса с помощью VBA Извлечение данных из Internet в режиме реального времени Анализ данных, извлеченных из Internet Условия выполнения метода OnTime Определение временного окна для выполнения макроса Отмена назначенного задания Отмена всех назначенных заданий Выполнение макроса по прошествии заданного периода времени Периодическое выполнение макроса через определенные промежутки времени Размещение данных на WebYстранице Создание WebYстраниц с помощью VBA Применение Excel в качестве системы управления содержимым Загрузка WebYстраницы на FTPYсервер Следующий шаг 400 401 402 402 404 405 407 407 407 409 410 413 414 414 415 415 416 416 416 418 420 421 425 425 Глава 15. Поддержка XML в профессиональном выпуске Excel 2003 Введение в XML Правила XML Универсальный формат файлов XML набирает обороты Схемы и сопоставления XML Сохранение и считывание содержимого рабочей книги Excel в формате XML Следующий шаг 427 427 428 429 429 429 Глава 16. Автоматизация Word Раннее связывание 439 439 Ошибка компиляции: отсутствие библиотеки Позднее связывание Работа с объектами Ключевое слово New Функция CreateObject Функция GetObject Объекты Word Объект Document 431 438 442 442 443 443 444 444 445 446 17 18 Содержание Объект Selection Объект Range Закладки 448 449 453 Следующий шаг 460 Часть III. Удивительные возможности Visual Basic for Applications Глава 17. Массивы Объявление массива Многомерные массивы Заполнение массива Манипулирование элементами массива Еще одно преимущество массивов Динамические массивы Передача массива в качестве параметра Следующий шаг Глава 18. Работа с текстовыми файлами Импорт данных из текстового файла Импорт текстовых файлов, содержащих менее 65 536 записей Импорт текстовых файлов, содержащих более 65 536 записей Экспорт данных в текстовый файл Следующий шаг Глава 19. Использование Microsoft Access ADO и DAO Объекты ADO Добавление записи в таблицу Access Извлечение записей из таблицы Access Обновление записей таблицы Access Удаление записей таблицы Access Создание итоговых запросов Несколько полезных макросов Проверка существования таблицы в базе данных Access Проверка существования поля в таблице базы данных Access Добавление таблицы в базу данных Access Добавление поля в таблицу базы данных Access Следующий шаг Глава 20. Создание пользовательских объектов, типов и коллекций Создание модуля класса Обработка событий уровня приложения и встроенной диаграммы События уровня приложения События встроенной диаграммы 461 463 463 464 464 466 467 469 470 471 473 473 474 482 486 487 489 490 492 493 494 496 499 499 500 500 501 502 503 503 505 506 506 507 509 Содержание Создание пользовательского объекта Применение пользовательского объекта на практике Использование выражений Property Let и Property Get Коллекции Создание коллекции в стандартном модуле Создание коллекции в модуле класса Создание пользовательских типов Следующий шаг Глава 21. Пользовательские формы //// профессиональный подход Панель инструментов UserForm Создание коллекций элементов управления формы Дополнительные элементы управления формы Переключатели Набор вкладок Поле ввода адреса диапазона ячеек Немодальные формы Гиперссылки в формах Добавление элементов управления на форму во время выполнения программного кода Изменение размеров формы во время выполнения программного кода Добавление элемента управления на форму во время выполнения программного кода Определение размера и положения элемента управления на форме во время выполнения программного кода Ограничения, связанные с добавлением элементов управления на форму во время выполнения программного кода Типы элементов управления Добавление изображения на форму во время выполнения программного кода Результирующий код Использование полосы прокрутки для выбора значений Добавление подсказки к элементу управления Использование сочетаний клавиш Подсказка элемента управления Порядок переноса фокуса Изменение цвета фона активного элемента управления Использование эффекта прозрачности формы Следующий шаг Глава 22. Интерфейс прикладного программирования (API) Windows Знакомство с Windows API Объявления Windows API Использование объявлений Windows API Примеры полезных объявлений Windows API Определение имени компьютера 510 511 513 515 515 516 520 524 525 525 526 528 528 529 530 531 531 532 534 534 535 535 535 536 537 539 541 541 541 541 542 545 546 547 547 548 548 549 549 19 20 Содержание Проверка возможности доступа к файлу Определение разрешения экрана Блокирование кнопки закрытия окна приложения Блокирование кнопки закрытия окна формы Часы Создание гиперссылок Воспроизведение звуковых файлов Создание диалогового окна выбора файла Дополнительные источники объявлений Windows API Следующий шаг Глава 23. Обработка ошибок Отладка кода с помощью редактора VBA Отладка кода пользовательской формы Обработка ошибок с помощью выражения On Error GoTo Использование нескольких обработчиков ошибок Универсальные обработчики ошибок Игнорирование ошибок Игнорирование сообщений Excel Извлечение пользы из ошибок Общение с заказчиками “Отложенные” ошибки Ошибка времени выполнения 9: “Subscript out of range” Ошибка времени выполнения 1004: “Method 'Range' of object '_Global' failed” Несовершенство защиты проекта VBA Защита проекта VBA в различных версиях Excel Совместимость различных версий Excel Следующий шаг Глава 24. Создание пользовательских меню и панелей инструментов Создание пользовательского меню Создание и удаление пользовательского меню Добавление команд меню Группирование команд меню Создание подменю Создание пользовательской панели инструментов Создание и удаление пользовательской панели инструментов Добавление кнопок на панель инструментов Выбор значка кнопки панели инструментов Добавление раскрывающегося списка на панель инструментов Сохранение и восстановление координат панели инструментов Другие способы запуска макросов Запуск макроса с помощью сочетания клавиш Запуск макроса с помощью кнопки Запуск макроса с помощью элемента управления ActiveX Следующий шаг 550 550 552 552 553 554 555 555 559 559 561 561 562 564 565 566 566 568 568 569 569 570 571 572 573 573 574 575 575 575 577 578 579 581 582 582 584 584 585 587 587 587 590 592 Содержание Глава 25. Надстройки Стандартные надстройки Excel Преобразование рабочей книги Excel в надстройку Преобразование рабочей книги Excel в надстройку с помощью диалогового окна “Сохранение документа” (Save As) Преобразование рабочей книги Excel в надстройку с помощью редактора VBA Использование надстроек Безопасность стандартных надстроек Excel Выгрузка надстроек Удаление надстроек Альтернативное решение: использование скрытой рабочей книги Следующий шаг Глава 26. Практикум: создание приложения Excel ‘‘с нуля’’ О Тушаре Мехта Постановка задачи Решение Реализация решения с помощью Excel и VBA Этап 1а: нисходящее программирование Этап 1б: создание ключевых компонентов Этап 2а: нисходящее программирование Этап 2б: создание ключевых компонентов Этап 3а: нисходящее программирование Этап 3б: создание ключевых компонентов Резюме Предметный указатель 593 593 594 594 596 597 598 599 599 599 601 603 603 604 605 606 606 607 608 610 611 613 614 615 21 Об авторах Билл Джелен, более известный под псевдонимом ‘‘Мистер Excel’’, YYYY рукоY водитель популярнейшего WebYсайта MrExcel.com (свыше 10 млн. посещений в год), а также автор многочисленных книг, посвященных Excel. Билл создает решения, основанные на использовании Excel VBA, для сотен клиентов по всему миру. До основания MrExcel.com Билл на протяжении 12 лет занимал должность финансового аналитика в отделах финансов, маркетинга, бухгалY терского учета и операций крупной корпорации. Билл проживает в окрестноY стях города Акрон, штат Огайо, США, вместе со своей женой Мэри Эллен и детьми Джошем и Зиком. Трейси Сирстад работает программистом и консультантом в компании Билла Джелена MrExcel Consulting. Трейси проживает в живописной местноY сти Южной Дакоты вместе со своим мужем Джоном и собакой Генералом. Посвящения Посвящается Мэри Эллен Джелен. Билл Посвящается Джону Сирстаду, вера которого в меня помогает преодолевать все трудности, встречающиеся на жизненном пути. Трейси Благодарности Спасибо Мале Сингху (Mala Singh) из XLSoft Consulting за помощь в написаY нии главы 10; Тому Уртису (Tom Urtis) за техническое редактирование; Джерри Колю (Jerry Kohl) за его великолепные идеи; Джанет Гарсиа (Jeanette Garcia), Барбе Джелен (Barb Jelen), Дагу и Стейси Джеффериз за техническую помощь; Зику, Джошу и Мэри Эллен Джелен за их терпение; Тому Мацешеку (Tom MaY cioszek) за ‘‘дружескую’’ проверку; Чеду Ротшиллеру (Chad Rothschiller) из MiY crosoft за неоценимую помощь в изучении Excel XML; Дейву Гейнеру (Dave Gainer), Стиву Заске (Steve Zaske), Эрику Паттерсону (Eric Patterson) и Джо ЧиY рилову (Joe Chirilov) из Microsoft; Лоретте Йейтс (Loretta Yates), Шону Диксону (Sean Dixon), Марго Кэттс (Margo Catts), Энди Бистеру (Andy Beaster), Грегу Виганду (Greg Wiegand), Эми Сорокас (Amy Sorokas), Ким Спилкер (Kim Spilker), Эрике Миллен (Erika Millen), Кэти Бидуэл (Kathy Bidwell), Синди ТиY терс (Cindy Teeters), Мишель Митчелл (Michelle Mitchell) и Гэри Адэру (Gary Adair) из Pearson; Иване Тейлор (Ivana Taylor) за блестящий маркетинг; читатеY лям MrExcel.com, нашим клиентам и всем MVP; Дэну Бриклину (Dan Bricklin), Бобу Фрэнкстону (Bob Frankston) и Митчу Кейпору (Mitch Kapor) за создание электронных таблиц; Уильяму Брауну (William Brown) из Waterside; Пэм Гензель 24 Благодарности (Pam Gensel) за 1Yй урок по созданию макросов; Роберту Ф. Джелену (Robert F. Jelen) за то, что он был первым поклонником моего таланта программиста; Роберту К. Джелену (Robert K. Jelen) за вдохновение; Бонни Хильярд (Bonnie Hilliard) за связь с общественностью; Лаурель Рииппу (Laurelle Riippa) из PW; Лео Лапорте (Leo LaPorte) и Фон Луу (Fawn Luu) из TechTV; Дэну Пойнтеру (Dan Poynter); Крейгу Кросмэну (Craig Crossman) из Computer America и УолтеY ру Моссбергу (Walter Mossberg) из Wall Street Journal. Билл Спасибо Корту ЧиллдонуYХоффу (Cort ChilldonYHoff) за поддержку в трудY ные минуты; Хуану Пабло Гонсалесу Руизу (Juan Pablo Gonzales Ruiz) за его советы (в частности, касающиеся функций из главы 4); Даниелю Клэнну (Daniel Klann), Деннису Валентайну (Dennis Wallentin), Ивану Ф. Моале (Ivan F. Moala), Хуану Пабло Гонсалесу (Juan Pablo Gonzales), Масаре Каджи (Masaru Kaji), Натану П. Оливеру (Nathan P. Oliver), Ричи Силлсу (Richie Sills), Расселу Гауфу (Russell Hauf), Суату Мехмету Озгуру (Suat Mehmet Ozgur), Тому Уртису (Tom Urtis), Томми Майлзу (Tommy Miles) и Вэю Цзянгу (Wei Jiang) за их вклад в написание главы 13; Крису Лемэру (Chris Lemair) за то, что он открыл для меня удивительный мир Excel и макросов, а также Энн Трой (Anne Troy) за то, что она познакомила меня с Биллом. Трейси Ââåäåíèå VBA — работа на результат Язык программирования Visual Basic for Applications (VBA) позвоY ляет существенно повысить произY водительность труда пользователей Microsoft Excel. Не дожидаясь помощи от отдела информационных технологий, польY зователи Excel могут самостоятельно создавать отчеты, необходимые для выполнения своих повседневных обязанностей. Это сулит как преY имущества, так и недостатки. С одY ной стороны, пользователи Excel смогут повысить эффективность своеY го труда. С другой, им придется раY зобраться со всеми тонкостями исY кусства создания макросов с помоY щью Excel VBA. Уверен, что в этот самый момент вы или ктоYлибо из ваших коллег все еще выполняете в Excel рутинY ные операции, которые могут быть автоматизированы с помощью VBA. Случай, произошедший с Валери, — весьма типичен для компаний, наY считывающих 20 и более пользоватеY лей Excel.  ÝÒÎÌ ÂÂÅÄÅÍÈÈ... VBA — работа на результат ......... 25 Как организована эта книга ........27 Для кого предназначена эта книга................................................. 28 История развития электронных таблиц и макросов ...................................... 29 Будущее Excel и VBA ..................... 30 Соглашения, принятые в этой книге...................................... 31 Рассматриваемые версии Excel .................................... 32 Программный код......................... 32 Следующий шаг............................. 32 26 Введение Практикум Создание финансового отчета Этот практикум основан на реальных событиях. После внедрения дорогостоящей системы планирования и управления ресурсами (ERP) корпорация средних раз& меров осталась без средств для создания ежемесячных финансовых отчетов. Ва& лери, занимающая должность бизнес&аналитика в финансовом отделе корпора& ции, приняла решение создать требуемый отчет самостоятельно. Прежде всего, Валери экспортировала главную бухгалтерскую книгу из ERP& системы в текстовый файл с разделителями&запятыми (CSV). Затем полученный CSV&файл был импортирован в Excel. Создание отчета оказалось делом не из легких. Некоторые счета необходимо было классифицировать как расходы, некоторые — полностью исключить из отчета. Шаг за шагом, Валери внесла все требуемые корректировки. Для получения пер& вой части отчета она создала сводную таблицу и скопировала итоговые значения на новый рабочий лист. Аналогичным образом были получены остальные части отчета. После трех часов кропотливого труда финансовый отчет был готов. Звездный час Валери передала отчет своему начальнику, который уже отчаялся получить его в срок. Она тут же стала “героем дня” и пребывала на седьмом небе от счастья. Неожиданный поворот событий На следующий день состоялось ежемесячное собрание руководства корпорации, пол& ноценно подготовиться к которому смог лишь начальник финансового отдела. Он эф& фектно положил на стол отчет, чем привел в замешательство всех присутствующих. По& сле того как начальник Валери рассказал о происхождении отчета, президент корпора& ции попросил его помочь подготовить отчеты для всех остальных отделов. Тяжкое бремя славы Корпорация, в которой работает Валери, насчитывает 46 отделов. Подготовка фи& нансового отчета для каждого отдела подразумевает импортирование данных из ERP&системы, удаление определенных счетов, создание нескольких сводных таб& лиц и комбинацию полученных итоговых результатов. На подготовку первого от& чета Валери потратила 3 часа. Она подсчитала, что с учетом полученного опыта сможет создать 46 отчетов не менее чем за 40 часов. Валери пришла в отчаяние. VBA спешит на помощь К счастью, Валери решила поделиться своими заботами с возглавляемой мною компанией MrExcel Consulting. Менее чем за неделю я создал набор VBA& макросов, реализующих действия, необходимые для подготовки отчета, — импорт данных, удаление счетов, создание сводных таблиц, объединение полученных итоговых сведений и стилевое форматирование отчета. Благодаря VBA 40& часовой процесс создания отчетов вручную был сведен к двум щелчкам мыши и четырем минутам ожидания. Введение Как организована эта книга Эта книга состоит из трех частей. Ее цель — научить читателя создавать макросы VBA для автоматизации выполнения рутинных задач в Excel. Часть I, “Первые шаги” Глава 1, ‘‘Excel и VBA YYYY гремучая смесь’’, акцентирует внимание на фундаменY тальной проблеме средства записи макросов Excel — средство записи макросов не работает. В главе 2, ‘‘Знакомство с Visual Basic for Applications’’, рассматриваются основы синтаксиса языка программирования Visual Basic for Applications. Глава 3, ‘‘Работа с диапазоном ячеек’’, посвящена работе с диапазонами ячеек. В главе 4, ‘‘Функции, определенные пользователем’’, рассматривается созY дание функций, определенных пользователем, а также приводятся примеры решения 25 наиболее распространенных задач, встречающихся при повсеY дневном программировании в Excel. Глава 5, ‘‘Циклы и управление выполнением кода’’, посвящена циклам — фундаментальному компоненту любого языка программирования. РазрабатыY вая решение для Валери, мы создали код, подготавливающий отчет для одного отдела, а затем поместили его в цикл с 46 итерациями. В главе 6, ‘‘Стиль записи ссылок R1C1’’, описывается стиль записи ссылок R1C1, а в главе 7, ‘‘Имена’’, — использование имен. Глава 8, ‘‘События’’, поY священа событиям, а глава 9, ‘‘Введение в пользовательские формы’’, — польY зовательским формам. Часть II, “Автоматизация Excel” В главе 10, ‘‘Диаграммы’’, рассматривается использование VBA при работе с диаграммами. Глава 11, ‘‘Анализ данных с помощью расширенного фильтY ра’’, посвящена анализу данных с помощью расширенного фильтра, а глаY ва 12, ‘‘Сводные таблицы’’, — работе со сводными таблицами. В комбинации с VBA диаграммы, расширенный фильтр и сводные таблицы образуют мощY ную основу для создания всевозможных отчетов. В главе 13, ‘‘Excel всемогущий’’, рассматриваются распространенные задаY чи, встречающиеся при работе с Excel, и их решения с помощью VBA, предY лагаемые опытными программистами со всех уголков мира. Глава 14, ‘‘Взаимодействие с Internet’’, посвящена автоматизации WebYзаY просов, глава 15, ‘‘Поддержка XML в профессиональном выпуске Excel 2003’’, — работе с данными в формате XML, глава 16, ‘‘Автоматизация Word’’, — автоматиY зации Microsoft Word. Часть III, “Удивительные возможности Visual Basic for Applications” В главе 17, ‘‘Массивы’’, рассматриваются массивы. Основное предназнаY чение массива заключается в упрощении обработки данных и повышении 27 28 Введение скорости выполнения программного кода. Глава 18, ‘‘Работа с текстовыми файY лами’’, посвящена работе с текстовыми файлами, а глава 19, ‘‘Использование Microsoft Access’’, — использованию баз данных Microsoft Access. Применение Excel в качестве пользовательского интерфейса, а MDBYфайла — в качестве базы данных позволяет добиться оптимального использования возможностей обеих программ. В главе 20, ‘‘Создание пользовательских объектов, типов и коллекций’’, расY сматривается создание модулей классов, предназначенных для размещения польY зовательских объектов VBA. Глава 21, ‘‘Пользовательские формы YYYY профессиоY нальный подход’’, посвящена сложным элементам управления, а также различY ным приемам программирования пользовательских форм. В главе 22, ‘‘Интерфейс прикладного программирования (API) Windows’’, рассматриваются основы исY пользования функций интерфейса прикладного программирования (API) WinY dows. Глава 23, ‘‘Обработка ошибок’’, посвящена обработке ошибок, глава 24, ‘‘Создание пользовательских меню и панелей инструментов’’, — созданию польY зовательских меню и панелей инструментов, глава 25, ‘‘Надстройки’’, — применеY нию надстроек. Наконец, глава 26, ‘‘Практикум: создание приложения Excel ‘‘с нуля’’, представляет собой практикум, демонстрирующий процесс создания приложения Excel ‘‘с нуля’’. Для кого предназначена эта книга На мероприятии, посвященном выходу на рынок пакета приложений MiY crosoft Office 2003, корпорация Microsoft огласила результаты исследования, согласно которым среднестатистический пользователь применяет только 10% заложенных в Office возможностей. Эта книга предназначена для опытных пользователей Excel. Опрос, проведенный среди 2000 посетителей WebYсайта MrExcel.com, показал, что 42% опытных пользователей Excel применяют в поY вседневной работе все наиболее эффективные средства этого приложения. Компания MrExcel Consulting часто устраивает семинары для бухгалтеров. Как правило, все они работают с Excel по 30YY40 часов в неделю. Практически на каждом семинаре я демонстрирую слушателям возможности Excel, о которых они ранее и не подозревали, и тем не менее практически на каждом семинаре находится слушатель, который превосходит меня в знании того или иного средства Excel. Что я хочу этим сказать? Вероятно, читатель этой книги велиY колепно разбирается в Excel. Несмотря на это, я предполагаю, что материал каждой главы незнаком в полном объеме для 58% опытных пользователей ExY cel. Прежде чем продемонстрировать решение той или иной задачи с помоY щью VBA, я кратко остановлюсь на ее выполнении с помощью пользовательY ского интерфейса Excel. Введение История развития электронных таблиц и макросов Вплоть до 1978 года каждый бухгалтер применял для создания отчета буY магу формата ‘‘гроссбух’’, механический карандаш и ластик. Сведения о дневY ном обороте записывались от руки, а промежуточный итог подсчитывался с помощью счетной машины. Ошибка в расчетах или исходных данных стоила многих часов работы с ластиком, счетной машиной и карандашом. В 1979 году Дэн Бриклин (Dan Bricklin) и Боб Фрэнкстон (Bob Frankston) (рис. 1) в буквальном смысле изменили мир. Они создали первую электронY ную таблицу, предназначенную для выполнения на компьютерах Apple II, и назвали ее VisiCalc (сокращение от англ. ‘‘visual calculator’’ — визуальный калькулятор). Вскоре программа VisiCalc была перенесена на несколько разY личных платформ, включая IBM PC. В 1981 году была выпущена расширенная версия VisiCalc, предназначенная для выполнения на компьютерах Apple III и поддерживающая макросы командной строки. Проект VisiCalc прекратил свое существование в 1985 году в результате судебной тяжбы. Рис. 1. Дэн Бриклин и Боб Фрэнкстон В 1983 году Митч Кейпор (Mitch Kapor) создал программу Lotus 1Y2Y3. По своим функциональным возможностям Lotus 1Y2Y3, изначально разработанY ная для выполнения под управлением операционной системы DOS, намного превзошла VisiCalc. В первый год объем продаж Lotus 1Y2Y3 достиг впечатляюY щей цифры в 53 млн долларов. Вплоть до середины 90Yх годов Lotus 1Y2Y3 заниY мала лидирующее положение на рынке программ для работы с электронными 29 30 Введение таблицами. Несмотря на наличие конкурентов (Quattro, Multiplan и др.), Lotus 1Y2Y3 ‘‘деYфакто’’ оставалась стандартным инструментом бухгалтерского учета. 1985 год был ознаменован появлением на свет второго выпуска Lotus 1Y2Y3, поддерживающего 8192 строки и 256 столбцов — более 2 млн ячеек! Кроме того, пользователю была предоставлена возможность записывать простые макросы. В 1990 году я был на 100% уверен в незыблемости позиций Lotus 1Y2Y3 на рынке программного обеспечения для работы с электронными таблицами. В начале 90Yх годов была выпущена версия Lotus 1Y2Y3 для операционной системы CP/M. В то же самое время Microsoft направила усилия на улучшение собственного продукта для работы с электронными таблицами — Excel. ПроY грамма Excel 3.0, выпущенная в 1990 году, существенно проигрывала Lotus 1Y2Y3. Тем не менее, Microsoft продолжала упорствовать, выпуская новую, улучшенную версию Excel каждые 1YY2 года. Excel 4, выпущенная в 1992 году, уже пользовалась популярностью и предлагала возможность создавать макроY сы с помощью языка XLM. Excel 5, выпущенная в 1993 году, поддерживала создание нескольких рабочих листов в пределах одной рабочей книги, а также запись макросов с помощью нового языка программирования — VBA. Благодаря наличию обратной совместимости с Lotus 1Y2Y3 продажи Excel стали стремительно возрастать. Середину 90Yх годов прошлого столеY тия можно без преувеличения назвать ‘‘золотой эрой’’ в развитии Excel. В Excel 95 и Excel 97 были представлены новые функциональные средства, такие как сводные таблицы, автофильтр и автоматическое вычисление проY межуточных итогов. Кроме того, в Excel 97 появилась новая среда разработY ки VBA. Доминированию Lotus 1Y2Y3 на рынке программ для работы с элекY тронными таблицами был положен конец. На момент написания этой книги гремучая смесь в виде Excel и VBA прочно завоевала сердца более чем 400 млн пользователей по всему миру. Будущее Excel и VBA С каждым новым выпуском программа для работы с электронными таблиY цами StarOffice Calc приближается по предлагаемым возможностям к Excel. Один из наиболее существенных недостатков пакета StarOffice заключается в отсутствии поддержки VBA, равно как и любого другого языка создания макросов. Этот факт позволяет не рассматривать StarOffice Calc в качестве серьезного конкурента Excel. Наверняка многие слышали о том, что Microsoft собирается отказаться от VBA. На самом деле это маловероятно. По прошествии более чем 10 лет с моY мента появления VBA Microsoft все еще поддерживает макросы XLM (язык макросов, появившийся в Excel 4). К тому же Microsoft официально заявила о поддержке VBA в следующей версии Excel. Учитывая, что большинство польY зователей приобретают каждую вторую версию Office, VBA будет актуален по меньшей мере до 2009 года. Введение Наконец, с точки зрения маркетинга было бы нелепо отказываться от VBA как от ключевого компонента, обеспечивающего Excel тотальное преимущестY во над StarOffice Calc. В октябре 2003 года корпорация Microsoft официально объявила о новой инициативе, направленной на повышение безопасности предлагаемых MiY crosoft решений. Это заявление имеет весьма серьезное значение, поскольку печально известный вирус Melissa использовал для своего распространения макросы VBA текстового редактора Word. Пресса и власть имущие отреагироY вали мгновенно, поместив VBA в список ‘‘вымирающих’’ технологий. Если безопасность приложений пакета Office будет оставаться под угрозой, MicroY soft может быть вынуждена отказаться от VBA. Одной из отличительных особенностей пакета Office 2003 является подY держка языка программирования Visual Basic .NET. Язык Visual Basic 6 позвоY лял автоматизировать любую задачу в Excel XP с помощью VBA. В Excel 2003 некоторые задачи, такие как создание смартYдокумента или размещение соY держимого на панели Справочные материалы (Research Pane), можно автоY матизировать только из среды .NET. Учитывая вопросы безопасности, возникающие при использовании VBA, Microsoft может заменить его набором инструментов .NET Tools for Office. Это было бы роковой ошибкой. На текущий момент свыше 400 млн пользователей Office могут приобрести книгу, подобную этой, в течении недели изучить осноY вы создания макросов и начать разрабатывать собственные решения с помощью VBA. Производительность труда ‘‘белых воротничков’’ может существенно возY расти, а их зависимость от отдела информационных технологий — уменьшитьY ся. Если Microsoft заменит VBA набором инструментов .NET Tools for Office, коY нечный пользователь будет лишен возможности создавать макросы с помощью Excel. Кроме того, это нивелирует преимущество Excel перед конкурирующим продуктом StarOffice Calc, поддержка языка создания макросов в котором должY на появиться к 2007 году. Таким образом, инициатива по повышению безопасY ности приложений пакета Office может обернуться для Microsoft утратой лидиY рующих позиций на рынке программ для работы с электронными таблицами. Тем не менее, я уверен, что VBA останется с нами по крайней мере до конY ца этого десятилетия. К тому же навыки создания макросов с помощью Excel VBA не утратят своей актуальности в результате перехода на среду .NET, а синтаксис VBA не так уж сильно отличается от синтаксиса Visual Basic .NET. Соглашения, принятые в этой книге В этой книге приняты следующие обозначения. Курсив используется для выделения терминов, названий WebYсайтов, а также акцентирования внимания читателя. Моноширинным шрифтом выделяется код VBA, заголовки столбцов, ссылки, формулы, имена макросов, модулей, функций, процедур, 31 32 Введение переменных, констант, объектов, методов, свойств, файлов, адреY сов URL и пр. Для выделения названий элементов пользовательского интерфейса применяется следующий шрифт: Меню. Вдобавок к указанным обозначениям, каждая глава книги содержит специY альные фрагменты текста: ‘‘Практикум’’, ‘‘На заметку’’, ‘‘Совет’’ и ‘‘Внимание’’. ‘‘Практикум’’ содержит примеры решений реальных задач с использованием средств, описываемых в текущей главе. На заметку Так помечается информация, которая не относится к основной теме главы, однако является весьма интересной и полезной. Совет В этом фрагменте содержатся методы и приемы, позволяющие сэкономить время и усилия, которые потребуются для решения той или иной задачи. Внимание Будьте осторожны, если встретите такой фрагмент. Приводящиеся в нем сведения помогут вам избежать ошибок, а также сберечь время и нервы. Рассматриваемые версии Excel На момент написания этой книги текущей версией Excel была версия ExY cel 2003. За исключением главы 15, ‘‘Поддержка XML в профессиональном выпуске Excel 2003’’, большая часть приведенного в книге программного кода совместима с Excel 2002 и Excel 2000. Более подробно о совместимости разY личных версий Excel рассказывается в главе 23, ‘‘Обработка ошибок’’. Программный код Прилагаемый к книге программный код включает все рассматриваемые в ней примеры. Его можно загрузить по адресу: http://www.williamspublishing.com/Books/5-8459-0882-5.html. (Чтобы загрузить код, прилагающийся к англоязычному изданию этой книги, поY сетите WebYстраницу по адресу: http://www.mrexcel.com/getcode.html.) Следующий шаг В главе 1, ‘‘Excel и VBA YYYY гремучая смесь’’, рассматривается редактор ViY sual Basic и средство записи макросов Excel. Введение Ждем ваших отзывов! Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы цеY ним ваше мнение и хотим знать, что было сделано нами правильно, что можY но было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие замечания, которые вам хотелось бы выY сказать в наш адрес. Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бумажное или электронное письмо, либо просто посетить наш WebYсервер и оставить свои замечания там. Одним словом, любым удобным для вас спосоY бом дайте нам знать, нравится или нет вам эта книга, а также выскажите свое мнение о том, как сделать наши книги более интересными для вас. Посылая письмо или сообщение, не забудьте указать название книги и ее авторов, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию послеY дующих книг. Наши координаты: [email protected] EYmail: http://www.williamspublishing.com WWW: Адреса для писем: из России: из Украины: 115419, Москва, а/я 783 03150, Киев, а/я 152 33 Часть I I Первые шаги 1. Excel и VBA — гремучая смесь ...............................................37 2. Знакомство с Visual Basic for Applications......................... 59 3. Работа с диапазоном ячеек ................................................. 95 4. Функции, определенные пользователем........................111 5. Циклы и управление выполнением кода........................ 141 6. Стиль записи ссылок R1C1.................................................... 161 7. Имена ....................................................................................... 177 8. События ...................................................................................189 9. Введение в пользовательские формы .............................215 Глава 1 Excel è VBA — ãðåìó÷àÿ ñìåñü 1 Excel всемогущий Сочетание языка программироваY ния Visual Basic for Applications (VBA) и Microsoft Excel таит в себе огромY ные возможности. Удивительно, но об этом задумывались лишь немногие из почти что 400 миллионов пользоватеY лей Microsoft Office. С помощью VBA можно существенно упростить выY полнение практически любой задачи в Excel. К примеру, создание целого воY роха квартальных диаграмм может быть сведено всего лишь к нескольY ким щелчкам мыши. Камни преткновения Чтобы научиться программировать с помощью VBA, вам придется преY одолеть два барьера — несовершенное средство записи макросов Excel и чрезвычайно запутанный синтаксис языка программирования VBA. Средство записи макросов не работает! С середины 90Yх годов прошлого столетия корпорация Microsoft начаY ла доминировать на рынке программ для создания электронных таблиц. Несмотря на то что в части работы с электронными таблицами продукт Microsoft оказался действительно очень удачным (приверженцы Lotus Excel всемогущий ........................... 37 Камни преткновения..................... 37 Панель инструментов “Visual Basic” ...................................39 Безопасность макросов ............... 40 Запись, хранение и выполнение макросов ..................42 Выполнение макроса ....................43 Редактор Visual Basic .....................45 Изучение кода макроса ................50 Непредвиденные результаты .....52 Отчаяние .......................................... 57 Следующий шаг.............................. 57 38 Часть I Первые шаги 1Y2Y3 быстро научились с ним работать), записать корректно работающий макрос в Excel не удавалось практически никому. Неоспоримое преимущество языка программирования Microsoft VBA перед языком макросов Lotus 1Y2Y3 нивелировалось низким качеством средства записи макросов. Макрос, созданный накануне с помощью Lotus 1Y2Y3, прекрасно выполняY ется и сегодня. Аналогичный макрос, созданный с помощью Microsoft Excel, мог преподнести неприятный сюрприз. Многие из тех, кто пытался создать свой первый макрос в Excel, приходили в отчаяние. Visual Basic — это не BASIC Код, сгенерированный в результате создания моего первого макроса, не был похож ни на что, виденное мною ранее. Несмотря на то что я знал с полY дюжины различных языков программирования, так называемый ‘‘Visual Basic’’ оказался абсолютно неинтуитивным и даже приблизительно не напоY минал тот BASIC, который я изучал в школе. В 1995 году я уже в совершенстве освоил создание электронных таблиц. И вот компания, в которой я работал, приняла решение о переходе с Lotus 1Y2Y3 на Excel. Без преувеличения, я оказался в сложном положении: с одной стороны — средство записи макросов, которое не работает так, как надо, с другой — язык программирования, в котором я ничего не понимал. Эта книга задумывалась как пособие для тех, кто разбирается в создании электронных таблиц больше, чем остальные 90% его сотрудников. Знание каY когоYлибо языка программирования необязательно. Практика показывает, что знание таких языков, как BASIC, может скорее навредить, чем принести пользу. Нас должно объединять следующее: мы все пытались создать макрос в Excel и остались недовольны полученным результатом. Хорошие новости Многочисленные недостатки средства записи макросов не являются неY преодолимым препятствием на пути к постижению искусства программироY вания в Excel. Далее в книге будет рассказано, как исправить ошибки автомаY тически сгенерированного кода, а также как прочитать загадочный мануY скрипт, написанный на языке Visual Basic. Отличные новости Microsoft Visual Basic for Applications (VBA) — чрезвычайно мощный язык программирования. С его помощью можно продублировать абсолютно все действия, выполняемые посредством пользовательского интерфейса Excel, например, создание отчетов, построение диаграмм и т.п. Авторы книги работают в компании MrExcel Consulting, предлагающей усY луги по автоматизации процесса создания отчетов в Excel для огромного числа клиентов. В ходе своей работы мы часто сталкиваемся с очень похожими задаY Excel и VBA — гремучая смесь Глава 1 чами: успешно импортировав данные в Excel, наши клиенты хотели бы упроY стить долгий и утомительный процесс создания одних и тех же еженедельных, ежемесячных или ежеквартальных отчетов. Именно это и предлагает VBA. Часы, потраченные на программирование макросов, сводят создание отчетов к нескольким щелчкам мыши. Поистине царская награда! В этой главе будут рассмотрены причины некорректной работы средства заY писи макросов. В частности, будет рассмотрен макрос, который начинает сбоY ить на следующий день после своего создания. Не обращайте внимания на неY понятный вам код. Цель этой главы — показать фундаментальную проблему средства создания макросов Excel и познакомить вас со средой разработки VBA. Панель инструментов “Visual Basic” Панель инструментов Visual Basic — одно из основных средств, необходиY мых при написании макросов с помощью VBA. Чтобы отобразить ее на экраY не, выберите в меню Excel команду Вид Панели инструментов Visual Basic (View Toolbars Visual Basic) (рис. 1.1). Элементы управления Параметры безопасности Выполнить макрос Редактор сценариев Записать макрос Режим конструктора Редактор Visual Basic Рис. 1.1. Панель инструментов Visual Basic предоставляет интерфейс для выполне& ния и записи макросов Панель инструментов Visual Basic содержит несколько кнопок. Выполнить макрос (Run Macro). Отображает список доступных макросов. Записать макрос (Record Macro). Начинает процесс записи макроса и отображает панель инструментов Остановить запись (Stop Recording) (рис. 1.2). Рис. 1.2. Одна из самых маленьких панелей инструментов в Excel содержит одну из самых важных для записи работоспособного макроса кнопок (Относительная ссылка) Безопасность (Security). Отображает диалоговое окно Безопасность (Security) (см. раздел ‘‘Безопасность макросов’’, далее в этой главе). Редактор Visual Basic (Visual Basic Editor). Открывает редактор Visual Basic. 39 40 Часть I Первые шаги Элементы управления (Control Toolbox). Отображает панель инструменY тов с элементами управления, которые можно добавить на рабочий лист. Режим конструктора (Design Mode). Режим конструктора позволяет редактировать элементы управления, размещенные на рабочем листе. Редактор сценариев (Microsoft Script Editor). Открывает редактор WebY сценариев. Поскольку эта тема не имеет прямого отношения к VBA, она не будет рассматриваться в этой книге. Панель инструментов Остановить запись (см. рис. 1.2), которая отображаY ется на экране в результате щелчка на кнопке Записать макрос, содержит всего лишь 2 кнопки. Остановить запись (Stop Recording). Останавливает текущий процесс записи макроса. Относительная ссылка (Relative Reference). Указывает Excel на необY ходимость использования относительных ссылок вместо абсолютных. Безопасность макросов После того как макросы VBA стали использоваться злоумышленниками для распространения вирусов, Microsoft разработала новую политику безопасY ности, по умолчанию запрещающую выполнение макросов. Чтобы продолY жить изучение материала, нам потребуется изменить стандартную политику. Откройте диалоговое окно Безопасность (Security) (рис. 1.3), выбрав коY манду главного меню Excel Сервис Макрос Безопасность (Tools Macro Security). Microsoft различает 4 уровня безопасности: Очень высокая (Very High), Высокая (High) (используется по умолчанию), Средняя (Medium) и Низкая (Low). При выборе уровня безопасности Высокая запрещается выполY нение или редактирование всех неподписанных макросов. Чтобы начать создаY ние собственных макросов, выберите уровень безопасности Средняя. Уровень безопасности “Очень высокая” В соответствии с парадигмой безопасности Microsoft системный админиY стратор создает высокозащищенный сетевой каталог (так называемую песочницу (sandbox)) и определяет его как доверенное размещение. Все макроY сы, находящиеся в песочнице, считаются безопасными (их разрешается выY полнять), остальные макросы таят в себе потенциальную угрозу. Ключевым моментом этой парадигмы является предположение о невозможности комY прометации доверенного размещения. Уровень безопасности “Высокая” На этом уровне безопасности разрешается выполнение только доверенных макросов, т.е. макросов, имеющих цифровую подпись и происходящих из наY Excel и VBA — гремучая смесь Глава 1 дежного источника. Поскольку подписывание макроса подразумевает необхоY димость приобретения цифрового сертификата у уполномоченной на это орY ганизации (такой как VeriSign), уровень безопасности Высокая (High) являетY ся далеко не самым лучшим выбором при разработке собственных макросов. При открытии рабочей книги все находящиеся в ней неподписанные макросы будут попросту отключены. Рис. 1.3. Уровень безопасности Высокая выбран по умолчанию Уровень безопасности “Средняя” На уровне безопасности Средняя (Medium) решение об отключении потенциально опасных макросов принимается пользователем. Именно этот уровень безопасности рекомендуется применять при разработке собY ственных макросов. Конечно же, необходимость включать макросы при каждом открытии рабочей книги может несколько раздражать. С другой стороны, это последняя возможность защититься от разрушительного виY руса, который таится в ничем не приметной рабочей книге, присланной вам по электронной почте. Уровень безопасности “Низкая” На этом уровне безопасности защита от потенциально опасных макроY сов отсутствует. Теперь уже ничто не защитит вас от вируса, хранящегося в рабочей книге. Применение уровня безопасности Низкая (Low) крайне не рекомендуется. 41 42 Часть I Первые шаги Если необходимость включения собственных макросов при каждом открыY тии рабочей книги начала приводить вас в бешенство, подумайте о приобреY тении цифрового сертификата (см. выше). Запись, хранение и выполнение макросов Запись макросов весьма полезна на начальном этапе изучения языка макY росов. По мере накопления знаний и опыта потребность в записи макросов будет неуклонно уменьшаться. Чтобы начать запись макроса, выберите команду главного меню Excel Сервис Макрос Начать запись (Tools Macro Record New Macro) или щелкните на кнопке Записать макрос (Record Macro) панели инструментов Visual Basic. Перед тем как начать запись макроса, Excel отобразит диалоговое окно Запись макроса (Record Macro), показанное на рис. 1.4. Рис. 1.4. Задайте имя и сочетание клавиш для будущего макроса Диалоговое окно “Запись макроса” Введите имя макроса в поле Имя макроса (Macro name). В имени макроса не допускается использование пробела (таким образом, имя Макрос1 являетY ся допустимым, а имя Макрос 1 — нет). Старайтесь давать макросам значиY мые имена, например, КвартальныйОтчет. Имена наподобие Макрос1 явY ляются не слишком информативными. Задайте сочетание клавиш с помощью одноименного поля. К примеру, если ввести в поле Сочетание клавиш (Shortcut key) букву ‘‘п’’, записанный макрос можно будет выполнить путем нажатия комбинации клавиш <Ctrl+п>. С помощью раскрывающегося списка Сохранить в (Store macro in) выберите место хранения записываемого макроса: Личная книга макросов (Personal Macro Workbook), Новая книга (New Workbook) или Эта книга (This Workbook). Макросы, имеющие непосредственное отношение к текущей рабочей книге, рекомендуется сохранять в размещении Эта книга. Личная книга макросов (PERSONAL.XLS) создается при первом сохранении макроса в одноименном размещении. Это скрытая рабочая книга, которая загружается автоматически при каждом запуске Excel. Чтобы отобразить личY Excel и VBA — гремучая смесь Глава 1 ную книгу макросов на экране, выберите команду главного меню Excel Окно Отобразить (Window Unhide). Личная книга макросов подходит для хранения далеко не каждого макроса. В ней рекомендуется хранить макросы общего назначения, не имеющие непоY средственного отношения к конкретному рабочему листу или книге. После выбора места хранения макроса щелкните на кнопке OK. Чтобы заY кончить запись макроса, щелкните на кнопке Остановить запись (Stop ReY cording) одноименной панели инструментов. Выполнение макроса Для выполнения макроса достаточно нажать соответствующую комбинаY цию клавиш (если она определена) на клавиатуре. Макрос можно назначить также кнопке панели инструментов или элементу управления формы. Кроме того, выполнить макрос можно с помощью уже рассмотренной панели инстY рументов Visual Basic. Создание кнопки выполнения макроса Макросы общего назначения рекомендуется хранить в личной книге макY росов и запускать с помощью кнопки, вынесенной на панель инструментов. Чтобы создать кнопку выполнения макроса, следуйте приведенной ниже процедуре. 1. Щелкните на панели инструментов правой кнопкой мыши и выберите команду контекстного меню Настройка (Customize). 2. Перейдите во вкладку Команды (Commands) (рис. 1.5). Рис. 1.5. Чтобы добавить кнопку выполнения макроса на панель инструментов Excel, восполь& зуйтесь диалоговым окном Настройка 43 44 Часть I Первые шаги 3. Выберите категорию Макросы (Macros). 4. Выберите команду Настраиваемая кнопка (Custom Button) (со значком улыбающейся рожицы) и перетащите ее на панель инструментов. 5. Щелкните на помещенном на панель инструментов значке с улыбаюY щейся рожицей правой кнопкой мыши (не закрывайте диалоговое окно Настройка (Customize)). 6. Выберите команду контекстного меню Назначить макрос (Assign Macro), выберите макрос и щелкните на кнопке OK. 7. Закройте диалоговое окно Настройка. Назначение макроса элементу управления формы Макросы, имеющие непосредственное отношение к конкретной рабочей книге, рекомендуется хранить вместе с рабочей книгой и запускать с помоY щью элемента управления формы, помещенного на рабочий лист. Чтобы назначить макрос элементу управления формы, помещенному на рабочий лист, выполните следующие действия. 1. Отобразите панель инструментов Формы (Forms), выбрав команду главного меню Excel Вид Панели инструментов Формы (View Toolbars Forms). 2. Щелкните на кнопке Кнопка (Button). 3. Щелкните на рабочем листе левой кнопкой мыши и, удерживая ее (кнопку) нажатой, нарисуйте контур кнопки. Отпустите левую кнопку мыши. 4. Выберите требуемый макрос в диалоговом окне Назначить макрос объекту (Assign Macro) (рис. 1.6) и щелкните на кнопке OK. Рис. 1.6. Макрос, хранящийся вместе с рабочей книгой, рекомендуется назначить элементу управ& ления формы, помещенному на рабочий лист 5. Щелкните на только что созданной кнопке для выполнения макроса. Excel и VBA — гремучая смесь Глава 1 Редактор Visual Basic На рис. 1.7 показано типичное окно редактора Visual Basic, которое состоит из трех основных частей. Не беспокойтесь, если ваше окно редактора Visual Basic отличается от показанного на рисунке. Более подробно редактор Visual Basic рассматривается в следующих разделах главы. Рис. 1.7. Окно редактора Visual Basic Параметры редактора Visual Basic Редактор Visual Basic имеет несколько настраиваемых параметров. РасY смотрим те из них, которые относятся непосредственно к написанию кода. Настройка параметров редактора Visual Basic Чтобы настроить параметры редактора Visual Basic, выберите команду меY ню Tools Options (Сервис Параметры)1 и перейдите во вкладку Editor (Редактор). Из всех параметров, размещенных на этой вкладке, внимания заY служивает только один — Require Variable Declaration (Требовать объявления переменной). По умолчанию Excel не требует объявлять переменные, что споY 1 Примерный перевод. Редактор Visual Basic не русифицирован. YYYY Прим. ред. 45 46 Часть I Первые шаги собствует более быстрому написанию кода. С другой стороны, с помощью этого требования можно предотвратить ошибки ввода имен переменных. ПоY ступайте так, как посчитаете нужным. Использование цифровых подписей Если вам надоело постоянно подтверждать безопасность собственных макY росов, воспользуйтесь цифровой подписью, выбрав команду меню Tools Digital Signature (Сервис Цифровая подпись). Диспетчер проектов Диспетчер проектов содержит список всех открытых рабочих книг и загруY женных дополнительных модулей. Щелкнув на значке ‘‘плюс’’ рядом с узлом VBAProject (Проект VBA), можно увидеть папки Microsoft Excel Objects (Объекты Microsoft Excel), Forms (Формы), Class Modules (Модули классов) и Modules (Модули) (присутствует по умолчанию). Каждая папка содержит один или несколько компонентов. Чтобы просмотреть код компонента, щелкните на нем правой кнопкой мыши и выберите команду контекстного меню View Code (Просмотр кода). Такого же результата можно достичь путем двойного щелчка на названии компонента (за исключением форм, двойной щелчок на названии которых приводит к открытию формы в режиме конструктора). Чтобы отобразить окно диспетчера проектов, выберите команду меню Tools Project Explorer (Сервис Диспетчер проектов), нажмите комY бинацию клавиш <Ctrl+R> или щелкните на кнопке Project Explorer (Диспетчер проектов), расположенной на панели инструментов. Окно диспетчера проектов показано на рис. 1.8. Чтобы добавить к проекту модуль, щелкните на названии проекта правой кнопкой мыши, выберите коY манду контекстного меню Insert (Вставить), а затем — тип добавляемого модуля. Объекты Microsoft Excel По умолчанию проект состоит из модулей рабочих листов и модуля ЭтаКнига (ThisWorkbook). Код, имеющий непосредственное отношение к рабочему листу (например, код обработки событий листа), помещается в соответствуюY щий этому листу модуль. Модуль ЭтаКнига содержит код обработки событий рабочей книги. Об обработке событий речь идет в главе 8, ‘‘События’’. Формы Excel позволяет создавать формы для взаимодействия с пользователем. О формах речь идет в главе 9, ‘‘Введение в пользовательские формы’’. Модули При записи макроса Excel автоматически создает модуль, куда помещает код макроса. Именно в таких модулях хранится большая часть создаваемого вами кода. Excel и VBA — гремучая смесь Глава 1 Рис. 1.8. Диспетчер проектов содер& жит список всех модулей проекта Модули классов Модули классов Excel предназначены для создания пользовательских объY ектов. Помимо этого, модули классов позволяют программистам обмениватьY ся фрагментами кода, не вдаваясь в подробности работы последнего. О модуY лях классов речь идет в главе 20, ‘‘Создание пользовательских объектов, типов и коллекций’’. Окно свойств Окно свойств предназначено для редактирования параметров различных компонентов — рабочих листов, книг, модулей или элементов управления форм. Список параметров компонента зависит от его типа. Чтобы открыть окно свойств, выберите команду меню View Properties Window (Вид Окно свойств), нажмите клавишу <F4> или щелкните на кнопке Project Properties (Свойства проекта), расположенной на панели инструментов. Практикум Предположим, что вы работаете бухгалтером. Каждое утро вы получаете по элек& тронной почте текстовый файл с разделителями&запятыми, содержащий инфор& мацию о счетах за вчерашний день в столбцах СчетДата, СчетНомер, ПродавецНомер, КлиентНомер, ПродуктВыручка, СервисВыручка, ПродуктСтоимость (рис. 1.9). 47 48 Часть I Первые шаги Рис. 1.9. Файл Счет.txt Вы вручную импортируете этот файл в Excel, добавляете итоговый столбец, фор& матируете заголовки столбцов с помощью утолщенного шрифта и распечатываете полученный отчет для передачи менеджерам. Подготовка к записи макроса Описанная выше последовательность действий просто&таки напрашивается быть оформленной в виде макроса. Прежде чем приступить к его записи, составьте точный список выполняемых операций. В рассматриваемом случае он должен выглядеть так. 1. Выберите команду главного меню Excel Файл Открыть (File Open). 2. Отобразите содержимое папки, в которой хранится файл Счет.txt. 3. Выберите значение Все файлы (All Files) из раскрывающегося списка Тип файлов (Files of type). 4. Выберите файл Счет.txt. 5. Щелкните на кнопке Открыть (Open). 6. В группе Формат исходных данных (Original data type) диалогового окна Мастер текстов (импорт) — шаг 1 из 3 (Text Import Wizard — Step 1 of 3) устано& вите переключатель С разделителями (Delimited). 7. Щелкните на кнопке Далее (Next). 8. В группе Символом-разделителем является (Delimiters) диалогового окна Мастер текстов (импорт) — шаг 2 из 3 (Text Import Wizard — Step 2 of 3) сбросьте флажок Знак табуляции (Tab) и установите флажок Запятая (Comma). 9. Щелкните на кнопке Далее. 10.В группе Формат данных столбца (Column data format) диалогового окна Мастер текстов (импорт) — шаг 3 из 3 (Text Import Wizard — Step 3 of 3) уста& новите переключатель Дата (Date) и выберите из раскрывающегося списка значение ДМГ (DMY). 11. Щелкните на кнопке Готово (Finish) для импортирования файла. 12.Нажмите клавишу <End>, а затем — клавишу <↓>, чтобы переместиться на по& следнюю строку импортированных данных. Excel и VBA — гремучая смесь Глава 1 13.Нажмите клавишу <↓>, чтобы переместиться на итоговую строку. 14.Введите слово “Всего”. 15. Нажмите клавишу <→> 4 раза, чтобы переместиться в столбец E итоговой строки. 16.Щелкните на кнопке Автосумма (AutoSum) и нажмите комбинацию клавиш <Ctrl+Enter>, чтобы суммировать значения столбца ПродуктВыручка, остава& ясь при этом в той же ячейке. 17. Перетащите маркер заполнения по столбцам F и G, чтобы скопировать в них формулу суммирования. 18.Выделите строку 1 и щелкните на кнопке Полужирный (Bold), чтобы выделить заголовки столбцов путем утолщения шрифта. 19.Выделите итоговую строку и щелкните на кнопке Полужирный, чтобы выделить суммарные значения столбцов путем утолщения шрифта. 20.Нажмите комбинацию клавиш <Ctrl+A>, чтобы выделить все ячейки рабочего листа. 21.Выберите команду Формат Столбец Автоподбор ширины (Format Column AutoFit Selection). Теперь вы готовы к записи своего первого макроса. Создайте пустую рабочую книгу и сохраните ее под каким&нибудь описательным именем, например МакросИмпортаСчетов.xls. Щелкните в панели инструментов Visual Basic на кнопке Записать макрос (Record Macro) или выберите команду меню Сервис Макрос Начать запись (Tools Macro Record New Macro). Измените предлагаемое по умолчанию имя макроса Макрос1 на более инфор& мативное, например ИмпортСчета. Убедитесь, что макрос будет сохранен в раз& мещении Эта книга (This Workbook) и задайте сочетание клавиш для выполнения макроса, к примеру <Ctrl+и>. По умолчанию в поле Описание (Description) зано& сится ваше имя и дата создания макроса. Добавьте сюда текст, кратко описываю& щий предназначение макроса (рис. 1.10), и щелкните на кнопке OK. Рис. 1.10. Прежде чем начать запись макроса, задайте необходимые пара& метры в диалоговом окне Запись макроса (Record Macro) 49 50 Часть I Первые шаги Запись макроса Начиная с этого момента средство записи макросов фиксирует каждое совершен& ное вами действие. Постарайтесь не отклоняться от намеченной ранее последо& вательности операций. Если, к примеру, вы случайно переместитесь в столбец F вместо столбца E, а затем вернетесь обратно, созданный макрос будет старатель& но повторять эту ошибку при каждом своем запуске. Внимание Сопротивляйтесь желанию убрать из виду панель инструментов Остановить запись (Stop Recording). Если она будет вам мешать, перетащите ее в безо& пасное место (действие по перетаскиванию панели Остановить запись не вклю& чается в записываемый макрос). Если вы все же закроете панель инструментов Остановить запись, то для того чтобы завершить запись макроса, вам потребует& ся выбрать команду меню Сервис Макрос Остановить запись (Tools Macro Stop Recording). Выполните все действия, необходимые для создания отчета. Чтобы остановить запись макроса, щелкните на панели инструментов Остановить запись на одно& именной кнопке. Панель инструментов Остановить запись исчезнет из виду. Внимание Закрытие панели инструментов Остановить запись не приводит к остановке записи макроса. Этим вы только усложните себе жизнь, так как теперь для завершения записи макроса вам потребуется выбрать команду меню Сервис Макрос Остановить запись. Пришло время взглянуть на сгенерированный код макроса. Для этого от& кройте окно редактора Visual Basic, выбрав команду меню Сервис Макрос Редактор Visual Basic (Tools Macro Visual Basic Editor) или воспользовав& шись комбинацией клавиш <Alt+F11>. Изучение кода макроса Рассмотрим код, сгенерированный Excel в результате записи макроса. ОтY кройте редактор Visual Basic, воспользовавшись комбинацией клавиш <Alt+F11>. Щелкните на названии модуля Module1 проекта МакросИмпортаСчетов.xls правой кнопкой мыши и выберите команду контекстного меY ню View Code (Просмотр кода). Строки кода, начинающиеся со знака апостY рофа, являются комментариями и игнорируются Excel. Комментарии создаY ются на основе информации, введенной в окне Запись макроса (Record Macro) (сюда, в частности, относится сочетание клавиш, использующееся для вызова макроса). Excel и VBA — гремучая смесь Глава 1 Внимание Комментарий не определяет сочетание клавиш. Другими словами, изменив в комментарии сочетание клавиш <Ctrl+и> на <Ctrl+с>, вы ничего не добьетесь. Изменить сочетание клавиш можно только с помощью диалогового окна Макрос (Macro). Сгенерированный код макроса, как правило, выглядит достаточно опрятно (рис. 1.11). Все строки кода, отличные от строк комментариев, сдвинуты на 4 символа вправо. Если длина строки превышает 100 символов, средство запиY си макросов разбивает ее на несколько строк меньшей длины, дополнительно сдвигая их еще на 4 символа вправо. В месте разрыва строки помещаются симY волы пробела и знака подчеркивания. Поскольку физические размеры книги не позволяют поместить на странице строку длиной 100 символов, в приводимых далее примерах все строки будут разбиваться на границе в 60YY65 символов. ТаY ким образом, код на экране компьютера может несколько отличаться от приY водимого здесь. Рис. 1.11. Сгенерированный код макроса выглядит очень аккуратно Приведенные ниже 8 строк кода представляют собой 1 строку, разбитую на несколько фрагментов для удобочитаемости. Workbooks.OpenText Filename:= _ "C:\Счет.txt", Origin:=1251, StartRow:=1, _ DataType:=xlDelimited, TextQualifier:=xlDoubleQuote, _ ConsecutiveDelimiter:=False, Tab:=False, Semicolon:=False, _ Comma:=True, Space:=False, Other:=False, _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 1), _ 51 52 Часть I Первые шаги Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1)), _ TrailingMinusNumbers:=True Учитывая сказанное выше, средство записи макросов превратило 21Yшаговую процедуру создания отчета в 14 строк кода. Весьма неплохо! Совет Каждое действие, выполняемое посредством пользовательского интерфейса Excel, может быть описано с помощью одной или нескольких строк программного кода. А теперь протестируем созданный макрос. Вернитесь к интерфейсу Excel, воспользовавшись комбинацией клавиш <Alt+F11>. Закройте файл Счет.txt, не сохранив внесенных в него изменений. При этом у вас должна остаться отY крытой рабочая книга МакросИмпортаСчетов.xls. Выполните сохраненный макрос, нажав комбинацию клавиш <Ctrl+и>. Он должен сработать безукоризненно (рис. 1.12). Рис. 1.12. Создание отчета прошло без сучка и задоринки Непредвиденные результаты Предположим, что утром следующего дня вы получили по электронной почте новый файл Счет.txt. Запустив созданный накануне макрос с помоY щью сочетания клавиш <Ctrl+и>, вы были неприятно удивлены. Файл Счет.txt от 5 июня содержал сведения о 12Yти счетах, а файл Счет.txt от 6 июня — о 16Yти. Тем не менее, макрос поместил итоговую информацию в 14Yю строку, тем самым в точности воспроизведя действия, выполненные при его записи (рис. 1.13). Возможное решение: использование относительных ссылок По умолчанию средство записи макросов рассматривает все действия, соY вершаемые пользователем, как абсолютные. Если на определенном этапе Excel и VBA — гремучая смесь Глава 1 пользователь введет данные в 14Yю строку, записанный макрос всегда будет вводить эти данные в 14Yю строку. Поскольку исходная информация может располагаться на разном количестве строк, использование при записи макроY са абсолютных ссылок недопустимо. Рис. 1.13. Записанный накануне макрос не выдержал проверки на прочность. Вместо того, что& бы добавить итоговые сведения сразу же после информации о счетах, макрос добавил их в 14&ю строку Одним из возможных решений в данной ситуации является использование при записи макроса относительных ссылок. Абсолютные ссылки основаны на действительных адресах ячеек, например A1. Относительные ссылки основаны на позиции ячейки относительно другой ячейки. Например, ссылка R[16]C[-1] указывает на ячейку, которая нахоY дится на 16 строк ниже и на 1 столбец левее текущей ячейки. Практикум Запишем тот же макрос с использованием относительных ссылок. Закройте файл Счет.txt без сохранения изменений. В рабочей книге МакросИмпортаСчетов.xls создайте новый макрос, выбрав команду меню Сервис Макрос Начать запись (Tools Macro Record New Macro). Присвойте новому макросу имя ИмпортСчетаОтносительно и назначьте другое сочетание клавиш, например <Ctrl+т> (рис. 1.14). Импортируйте данные из файла Счет.txt. Прежде чем переходить к последней строке данных с помощью последовательного нажатия клавиш <End> и <↓>, щелкните на кнопке Относительная ссылка (Relative Reference) в панели инстру& ментов Остановить запись (Stop Recording) (см. рис. 1.2). Выполните следующие действия. 1. Нажмите клавишу <End>, а затем — клавишу <↓>, чтобы переместиться на по& следнюю строку импортированных данных. 53 54 Часть I Первые шаги 2. Нажмите клавишу <↓>, чтобы переместиться на итоговую строку. Рис. 1.14. Попытка номер 2 3. Введите слово “Всего”. 4. Нажмите клавишу <→> 4 раза, чтобы переместиться в столбец E итоговой строки. 5. Щелкните на кнопке Автосумма (AutoSum) и нажмите комбинацию клавиш <Ctrl+Enter>, чтобы суммировать значения столбца ПродуктВыручка, остава& ясь при этом в той же ячейке. 6. Перетащите маркер заполнения по столбцам F и G, чтобы скопировать в них формулу суммирования. 7. Выделите итоговую строку с помощью комбинации клавиш <Shift+пробел> и щелкните на кнопке Полужирный (Bold), чтобы выделить суммарные значения столбцов путем утолщения шрифта. Не спешите перемещаться в ячейку A1 и выделять заголовки столбцов путем утолщения шрифта. Средство записи макросов зафиксирует это действие как перемещение на 17 строк вверх, а это не совсем корректно. Отключите режим относительных ссылок, еще раз щелкнув на кнопке Относительная ссылка, и продолжите запись макроса. 8. Выделите строку 1 и щелкните на кнопке Полужирный, чтобы выделить заголов& ки столбцов путем утолщения шрифта. 9. Нажмите комбинацию клавиш <Ctrl+A>, чтобы выделить все ячейки рабочего листа. 10.Выберите команду Формат Столбец Автоподбор ширины (Format Column AutoFit Selection). 11. Остановите запись макроса. Нажмите комбинацию клавиш <Alt+F11>, чтобы вернуться в окно редактора Visual Basic и просмотреть полученный на этот раз код. Текст макроса ИмпортСчетаОтносительно будет помещен в модуль Module1 сразу же после текста макроса ИмпортСчета. Excel и VBA — гремучая смесь Глава 1 Внимание Если между созданием первого и второго макроса вы завершали работу с Ex& cel, новый макрос будет помещен в модуль Module2. На рис. 1.15 показан код макроса ИмпортСчетаОтносительно с двумя ком& ментариями, указывающими на момент включения и отключения режима от& носительных ссылок. Рис. 1.15. Код макроса, записанного с использованием режима относительных ссылок Чтобы протестировать макрос, закройте файл Счет.txt без сохранения изменений и нажмите комбинацию клавиш <Ctrl+т>. На этот раз работа макY роса не должна вызывать какихYлибо нареканий. Предположим, что файл Счет.txt от 7Yго июня содержит сведения о 21 счете (рис. 1.16). Откройте рабочую книгу МакросИмпортаСчетов.xls и выполните ноY вый макрос, нажав комбинацию клавиш <Ctrl+т>. На первый взгляд макрос справился с поставленной перед ним задачей. Но взгляните на рис. 1.17 — не кажется ли вам, что здесь чтоYто не так? Передав подобный отчет менеджеру, вы, несомненно, навредили бы своей репутации. Присмотритесь к ячейке E23. В левом верхнем углу ячейки нахоY 55 56 Часть I Первые шаги дится маленький зеленый треугольник — верный признак ошибки. Следует отметить, что возможность предупреждения ошибок появилась благодаря смартYтегам — средству, недоступному в Excel 95 или Excel 97. Рис. 1.16. Сможет ли новый макрос справиться с этими данными? Рис. 1.17. Результат выполнения макроса, использующего относительные ссылки Щелкните в ячейке E23 и подведите указатель к появившейся рядом с ячейY кой кнопке примечания. На экране появится сообщение о том, что формула в этой ячейке ссылается на диапазон, к которому прилегают другие значения. Взглянув на строку формул, вы увидите, что макрос суммировал значения тольY Excel и VBA — гремучая смесь Глава 1 ко с 7 по 22 строку. К сожалению, логику функции автоматического суммироваY ния не может воспроизвести ни один автоматически созданный макрос. Если же файл Счет.txt от 7Yго июня содержит сведения о меньшем колиY честве счетов, чем 6Yго июня, Excel ‘‘наградит’’ вас аналогичной формулой =СУММ(E10:E65531) (=SUM(E10:E65531)) и сообщением о наличии цикY лических ссылок (рис. 1.18). Рис. 1.18. Результат выполнения макроса, использующего относительные ссылки, при меньшем количестве счетов Отчаяние Дочитав книгу до этого места, вы, вероятно, уже проклинаете Microsoft. Представьте себе мое состояние после нескольких дней безуспешных попыток написать хотя бы один работающий макрос. Ситуацию усугубляло знание тоY го, что подобные макросы без проблем генерировались средством записи макY росов Lotus 1Y2Y3, созданным в далеком 1983 году. То, что получилось у Мича Кейпора (Mitch Kapor) 21 год назад, Microsoft не может повторить до сих пор. Известно ли вам, что все ранние версии Excel вплоть до Excel 97 поддержиY вали выполнение макросов командной строки Lotus? Этот факт стал известен мне только после того, как Microsoft объявила об окончании поддержки Excel 97. Многие компании, перешедшие на Excel XP (который уже не подY держивал выполнение макросов Lotus 1Y2Y3), обратились к нам с просьбой пеY реписать старые макросы Lotus на Excel VBA. Я не могу смириться с мыслью, что начиная с Excel 5, Excel 95 и Excel 97 интерпретатор Microsoft мог выполY нить макрос, корректно решавший поставленную нами задачу, однако средстY во записи макросов было не в состоянии его создать. Следующий шаг Единственно правильное решение рассмотренной задачи заключается в применении языка программирования Visual Basic. Первым приближением 57 58 Часть I Первые шаги к цели можно считать автоматически сгенерированный макрос. Немного здравого смысла, и он станет реальным подспорьем в решении повседневных задач. В главе 2, ‘‘Знакомство с Visual Basic for Applications” мы попробуем применить этот подход к двум записанным нами макросам. Научившись ‘‘читать’’ код VBA, вы с легкостью сможете подправить автоматически сгенеY рированный код и даже написать макрос ‘‘с нуля’’. Глава 2 Çíàêîìñòâî ñ Visual Basic for Applications 2 Загадочный код Код VBA способен смутить кажY дого, кто изучал в школе один из процедурных языков программироY вания наподобие BASIC или COBOL. Несмотря на то что VBA расшифроY вывается как ‘‘Visual Basic for ApplicaY tions’’, он представляет собой объект* но*ориентированную версию BASIC. Рассмотрим небольшой фрагмент кода VBA. Selection.End(xlDown).Select Range("A14").Select ActiveCell.FormulaR1C1 = "Всего" Range("E14").Select Selection.FormulaR1C1 = _ "=SUM(R[-12]C:R[-1]C)" Selection.AutoFill Destination:=Range("E14:G14"), _ Type:=xlFillDefault Бьюсь об заклад, что этот код не будет иметь ни малейшего смысла для тех, кто изучал только процедурY ные языки программирования (к соY жалению, практика изучения процеY дурных языков еще весьма популярна во многих учебных заведениях). Ниже приведен фрагмент кода, написанный на языке BASIC. For x = 1 to 10 Print Rpt$(" ", x); Print "*"; Next x В результате его выполнения на экY ране компьютера появится ‘‘лесенка’’ из символов звездочки. Загадочный код..............................59 Учимся понимать “речь” VBA .....60 Справочная система VBA .............63 Изучение кода записанного макроса ............................................66 Использование отладчика кода ...................................................74 Диспетчер объектов ......................85 5 советов по исправлению и оптимизации автоматически сгенерированного кода ................87 Исправление и оптимизация автоматически сгенерированного кода ................90 Следующий шаг..............................93 60 Часть I Первые шаги * * * * * * * * * * Синтаксис процедурного языка программирования больше похож на синY таксис английского языка, нежели синтаксис объектноYориентированного языка программирования. К примеру, выражение Print "Hello World" записано в привычном формате ‘‘глаголYYобъект’’. А теперь постараемся заY быть о программировании и рассмотрим один конкретный пример. Учимся понимать “речь” VBA Попробуем сыграть в футбол на языке BASIC. Команда ‘‘ударить по мячу’’ будет выглядеть примерно следующим образом: Kick the Ball Именно так мы и говорим в повседневной жизни. Глагол ‘‘ударить’’ (kick) следует перед существительным ‘‘мяч’’ (the ball). Аналогично, в приведенном выше примере глагол Print следует перед существительным * (звездочка). К сожалению, подобный синтаксис не употребляется ни в одном объектY ноYориентированном языке, включая VBA. Исходя из самого названия этого класса языков программирования, становится ясно, что центральное место здесь отводится объекту, т.е. существительному. Команда ‘‘ударить по мячу’’, записанная на языке VBA, будет выглядеть так: Ball.Kick В VBA существительное (объект) записывается перед глаголом (методом). Базовая структура большинства строк VBA выглядит так: Объект.Метод К сожалению, это не очень похоже на повседневную речь. Никто не говоY рит ‘‘Вода.Пить’’, ‘‘Мяч.Ударить’’ или ‘‘Девушка.Целовать’’. Именно поэтому VBA кажется очень сложным по сравнению с процедурными языками проY граммирования. Продолжим аналогию. Представьте, что вы стоите на зеленом газоне перед тремя мячами: футбольным, баскетбольным и бейсбольным. Как сказать на VBA ‘‘ударить футбольный мяч’’ члену школьной футбольной команды? Выше была приведена команда ‘‘ударить по мячу’’ (Ball.Kick), однако в данном случае этого недостаточно. Возможно, ребенок ударит мяч, который находится ближе всех к нему (например, бейсбольный). В VBA практически для каждого объекта (существительного) определяется коллекция этих объектов. Рассмотрим электронную таблицу Excel. Строке соY Знакомство с Visual Basic for Applications Глава 2 ответствует набор строк, столбцу YYYY набор столбцов, рабочему листу YYYY набор рабочих листов. С точки зрения синтаксиса имя коллекции объектов составY ляется из имени объекта и суффикса ‘‘s’’, например: Row Rows, Cell Cells, Ball Balls. Существует несколько способов обращения к элементу коллекции. ПерY вый из них состоит в использовании порядкового номера элемента, например: Balls(2).Kick Несмотря на то что приведенная выше запись вполне корректна, переY упорядочивание мячей в коллекции может привести к весьма плачевному результату. Второй способ обращения к элементу коллекции является более безопасY ным и состоит в использовании имени элемента, например: Balls("Soccer").Kick Теперь можно быть уверенным, что ребенок ударит именно по футбольноY му мячу. Для большинства методов (глаголов) в Excel VBA определены параметры, хаY рактеризующие способ выполнения метода (назовем их наречиями). Ниже привеY дена команда ‘‘сильно ударить футбольный мяч так, чтобы он полетел влево’’: Balls("Soccer").Kick Direction:=Left, Force:=Hard Комбинации двоеточия и знака равенства в коде VBA всегда указывают на параметр метода. Методы могут иметь много параметров, как обязательных, так и нет. ПредY положим, что у метода Kick есть параметр Elevation (‘‘поднятие’’). Ниже приведена команда ‘‘сильно ударить футбольный мяч так, чтобы он полетел высоко влево’’: Balls("Soccer").Kick Direction:=Left, Force:=Hard, Elevation:=High Для каждого метода существует определенный порядок следования его параY метров. Некоторые программисты пропускают имена параметров, указывая тольY ко их значения. Следующая строка кода полностью эквивалентна предыдущей: Balls("Soccer").Kick Left, Hard, High Практика пропуска имен параметров не вносит ясности в код, так как не зная точного порядка следования параметров, сложно судить о предназначеY нии той или иной строки. Значения параметров Left, Hard и High сами по себе информативны, однако так бывает далеко не всегда. Рассмотрим слеY дующую строку кода: WordArt.Add Left:=10, Top:=20, Width:=100, Height:=200 Если пропустить имена параметров, она будет выглядеть так: WordArt.Add 10, 20, 100, 200 Несмотря на то что приведенная выше строка кода вполне корректна, отY сутствие имен параметров серьезно затрудняет восприятие ее смысла. Точный 61 62 Часть I Первые шаги порядок следования параметров метода можно узнать, обратившись к разделу справочной системы, посвященному этому методу. Ситуацию усложняет еще и то, что имена параметров требуется указывать только в случае нарушения стандартного порядка их следования. Ниже привеY дены две эквивалентных строки кода, соответствующих команде ‘‘ударить футбольный мяч так, чтобы он полетел высоко влево’’ (не важно, насколько сильным будет сам удар): Balls("Soccer").Kick Direction:=Left, Elevation:=High Balls("Soccer").Kick Left, Elevation:=High Указав имя одного параметра, следует указать также имена всех параметY ров, которые последуют за ним в этой строке кода. Некоторые методы не имеют параметров. Ниже приведен код, имитируюY щий нажатие клавиши <F9>: Application.Calculate Другие методы выполняют действие и возвращают его результат. Ниже приведен код, добавляющий рабочий лист: Worksheet.Add Before:=Worksheets(1) Поскольку метод Worksheet.Add создает новый объект, результат его выполнения может быть присвоен переменной (параметры метода при этом следует взять в скобки): Set MyWorksheet = Worksheet.Add (Before:=Worksheets(1)) Напоследок рассмотрим еще одну важную составляющую языка VBA — свойства. Свойства описывают объект наподобие того, как прилагательное описывает существительное. Обратимся к примеру. В Excel существует объект, соответствующий активY ной ячейке YYYY ActiveCell. Предположим, что нам необходимо изменить цвет активной ячейки на желтый. Цвет ячейки определяется значением свойY ства Interior.ColorIndex объекта ActiveCell. Изменение цвета ячейки на желтый описывается следующей строкой кода: ActiveCell.Interior.ColorIndex = 6 Обратите внимание, что в приведенном выше коде используется конструкY ция Объект.Свойство, похожая на уже рассмотренную нами конструкцию Объект.Метод. На первый взгляд, их невозможно отличить друг от друга. Если же присмотреться повнимательнее, то можно заметить отсутствие двоеточия перед знаком равенства в строке с конструкцией Объект.Свойство. Обычно свойство всегда присутствует в левой или правой части выражений, связанных с присвоением значения. Ниже приведена команда, изменяющая цвет текущей ячейки на цвет ячейки A1: ActiveCell.Interior.ColorIndex = Range("A1").Interior.ColorIndex Итак, изменение значения свойства Interior.ColorIndex приводит к изменению цвета ячейки. Сравнивая свойство с прилагательным, получаем Знакомство с Visual Basic for Applications Глава 2 достаточно странный результат YYYY изменение прилагательного влечет за собой выполнение действия. В табл. 2.1 приведен краткий ‘‘словарь’’ терминов VBA. Таблица 2.1. Словарь терминов VBA Термин VBA Аналог Примечания Объект Имя существительное YYYY Коллекция Имя существительное во множественном числе Обычно указывается элемент коллекY ции, например Worksheets(1) Метод Глагол Объект.Метод Параметр Наречие Параметры указываются после имени метода. Между именем параметра и его значением ставится двоеточие и знак раY венства (:=) Свойство Имя прилагательное Обычно свойство присутствует в левой или правой части выражения, связанY ного с присвоением значения, например ActiveCell.Height = 10 или x = ActiveCell.Height Справочная система VBA Не беспокойтесь, если вы все еще не научились отличать метод от свойстY ва. Именно здесь нам пригодится раскритикованное в предыдущей главе средство записи макросов. Чтобы узнать, как запрограммировать то или иное действие, запишите его в виде макроса и затем изучите сгенерированный код. Спасительная клавиша <F1> Приступая к написанию макросов, обязательно убедитесь в наличии на вашем компьютере справочной системы VBA. К сожалению, она не входит в стандартную установку Microsoft Office. Чтобы проверить наличие справочной системы VBA, выполните следующие действия. 1. Запустите Excel и откройте окно редактора Visual Basic, воспользовавY шись комбинацией клавиш <Alt+F11>. Выберите команду меню Insert Module (Вставить Модуль) (рис. 2.1). 2. Введите 3 строки кода, как показано на рис. 2.2, и установите курсор посредине слова MsgBox. 3. Нажмите клавишу <F1>. Если справочная система VBA установлена, откроется окно, показанное на рис. 2.3. 63 64 Часть I Первые шаги Рис. 2.1. Вставьте в рабочую книгу новый модуль Рис. 2.2. Установите курсор посредине слова MsgBox и нажмите клавишу <F1> Рис. 2.3. Если справочная система VBA установлена, вы увидите на экране это окно Если справочная система VBA не установлена, Excel выдаст сообщение об ошибке. Установите справочную систему VBA, воспользовавшись установочY ными компактYдисками Microsoft Office (при необходимости обратитесь за помощью к системному администратору). Знакомство с Visual Basic for Applications Глава 2 Просмотр разделов справочной системы Раздел справочной системы, посвященный тому или иному методу, содерY жит подробное описание всех его параметров. Под именем метода или функY ции расположены три ссылки: See Also (См. также), Example (Пример) и Specifics (Особенности). Одной из наиболее полезных является ссылка Example, ведущая на страницу с примером использования метода или функY ции (рис. 2.4). Рис. 2.4. Большинство разделов справочной системы VBA со& держат ссылку на страницу с примерами Код примера можно выделить (рис. 2.5), скопировать в буфер обмена с поY мощью комбинации клавиш <Ctrl+C>, а затем вставить в модуль с помощью комбинации клавиш <Ctrl+V>. Код записанных макросов наверняка содержит много незнакомых объекY тов и методов. Установите курсор посредине интересующего вас ключевого 65 66 Часть I Первые шаги слова и нажмите клавишу <F1>, чтобы отобразить соответствующий раздел справочной системы VBA. Рис. 2.5. Выделите код примера и скопируйте его в буфер обмена с по& мощью комбинации клавиш <Ctrl+C> Изучение кода записанного макроса Рассмотрим код первого макроса, записанного в главе 1, “Excel и VBA — гремучая смесь”, и попытаемся понять его смысл в контексте объектов, свойств и методов (рис. 2.6). Согласно концепции Объект.Метод (или, что то же самое, Существитель* ное.Глагол) в 1Yй строке кода Workbooks является объектом, а OpenText — методом. Установите курсор внутри слова OpenText и нажмите клавишу <F1>, чтобы открыть раздел справочной системы VBA, посвященный этому методу (рис. 2.7). В справочной системе указано, что OpenText — это метод. Его параметY ры перечислены в стандартном порядке следования в области, выделенной серым цветом. Обратите внимание, что метод OpenText имеет всего лишь один обязательный аргумент YYYY FileName. Все остальные параметры могут быть пропущены. Знакомство с Visual Basic for Applications Глава 2 Рис. 2.6. Код записанного макроса Рис. 2.7. Раздел справочной системы, посвященный методу OpenText. Ссылка Applies To (Применяется к) позволяет просмотреть список объектов, к которым может быть применен этот метод 67 68 Часть I Первые шаги Необязательные параметры В справочной системе VBA можно найти информацию о стандартных знаY чениях необязательных параметров. К примеру, стандартным значением параY метра StartRow является 1, что весьма приемлемо. А вот пропустив параметр Origin, вы рискуете попасть впросак. Дело в том, что по умолчанию Excel исY пользует текущее значение этого параметра. Другими словами, если вы выполY ните свой макрос после того, как ктоYто импортирует в Excel файл с китайскими иероглифами, Excel предположит, что вы хотите сделать то же самое. Предопределенные константы Согласно разделу справочной системы VBA, посвященному методу OpenText (см. рис. 2.7), DataType — это свойство, которое может иметь значение xlDelimited или xlFixedWidth (предопределенные константы Excel VBA типа XlTextParsingType). В редакторе Visual Basic нажмите комбинацию клавиш <Ctrl+G>, чтобы открыть окно Immediate (Быстрое выполнение). В окне Immediate введите следующую строку и нажмите клавишу <Enter>: Print xlFixedWidth Как показано на рис. 2.8, значением константы xlFixedWidth является 2. Аналогичным образом можно узнать значение константы xlDelimited, которое равно 1. Использование предопределенных констант с информаY тивными именами вместо чисел значительно повышает удобочитаемость программного кода. Рис. 2.8. Воспользуйтесь окном Immediate, чтобы узнать значения предопределенных констант VBA, таких как xlFixedWidth В большинстве случаев раздел справочной системы либо содержит допусY тимые константы непосредственно в тексте справки, либо предлагает ссылку, щелчок на которой приводит к их отображению (рис. 2.9). К справочной системе VBA можно предъявить только одну претензию YYYY она не позволяет узнать, является ли конкретный параметр нововведением теY кущей версии Excel. К примеру, параметр TrailingMinusNumbers был впервые представлен в Excel 2002. Попытка выполнения макроса, содержаY щего этот параметр, в Excel 2000 завершится весьма плачевно. К сожалению, эта проблема достаточно серьезна, поскольку решить ее можно только метоY дом проб и ошибок. Знакомство с Visual Basic for Applications Глава 2 Рис. 2.9. Щелкните на ссылке, чтобы увидеть все допустимые константы Изучив раздел справочной системы, посвященный методу OpenText, можно заметить, что этот метод является в некотором смысле эквивалентом мастера импорта текстов. Так, на первом шаге мастера необходимо выбрать формат исходных данных YYYY С разделителями (Delimited) или Фиксированной ширины (Fixed width), YYYY а также формат файла и строку, с которой необходимо начать импорт (рис. 2.10). Другими словами, первый шаг мастера импорта текстов можно описать тремя параметрами метода OpenText: Origin:=1251 StartRow:=1 DataType:=xlDelimited 69 70 Часть I Первые шаги Рис. 2.10. Первый шаг мастера импорта текстов описывается тремя па& раметрами метода OpenText На втором шаге мастера импорта текстов производится выбор разделителя для текстовых данных. Чтобы Excel не считал две последовательные запятые одной, флажок Считать последовательные разделители одним (Treat conY secutive delimiters as one) снят. Поля, содержащие запятую как часть данных (например, ‘‘XYZ, Inc.’’), должны быть ограничены символом, выбранным в раскрывающемся списке Ограничитель строк (Text qualifier) (рис. 2.11). Рис. 2.11. Второй шаг мастера импорта текстов описывается семью па& раметрами метода OpenText Второй шаг мастера импорта текстов можно описать следующими параY метрами метода OpenText: Знакомство с Visual Basic for Applications Глава 2 TextQualifier:=xlDoubleQuote ConsecutiveDelimiter:=False Tab:=False Semicolon:=False Comma:=True Space:=False Other:=False На третьем шаге мастера импорта текстов определяется формат столбцов данных. В рассмотренном примере мы оставили стандартный формат Общий (General) для всех столбцов, кроме первого, для которого был выбран формат даты ДМГ (DMY) (рис. 2.12). Рис. 2.12. Третий шаг мастера импорта текстов описывается всего лишь одним параметром метода OpenText Третий шаг мастера импорта текстов полностью описывается параметром FieldInfo метода OpenText. Щелкнув на кнопке Подробнее (Advanced) диалогового окна Мастер текстов (импорт) — шаг 3 из 3 (Text Import Wizard — Step 3 of 3), можно выY брать разделитель целой и дробной части, разделитель разрядов, а также укаY зать на необходимость отображения знака ‘‘минус’’ в конце отрицательных чисел (рис. 2.13). Следует отметить, что средство записи макросов не генерирует код для паY раметров DecimalSeparator и ThousandsSeparator до тех пор, пока не будет выбран отличный от стандартного разделитель целой и дробной части и разделитель разрядов, соответственно. В то же время, средство записи макроY сов всегда генерирует код для параметра TrailingMinusNumbers. Как видите, практически каждое действие, выполняемое с помощью польY зовательского интерфейса Excel, находит отражение в фрагменте программY ного кода макроса. 71 72 Часть I Первые шаги Рис. 2.13. В диалоговом окне Дополнительная настройка импорта текста (Advanced Text Im& port Settings) можно определить три параметра метода OpenText Рассмотрим следующую строку: Selection.End(xlDown).Select Щелкните на слове End и нажмите клавишу <F1>. На экране появится диалоговое окне Context Help (Контекстная справка), предлагающее выбрать один из двух разделов справочной системы, посвященный слову End. Один из них находится в библиотеке Excel, а другой YYYY в библиотеке VBA (рис. 2.14). Рис. 2.14. Иногда одному ключевому слову соответст& вует несколько разделов справочной системы Чтобы не гадать, какой из двух разделов справочной системы вам нужен, щелкните на кнопке Help (Справка). Как показано на рис. 2.15, раздел спраY вочной системы из библиотеки VBA содержит сведения о выражении End. Это не то, что нам нужно. Закройте окно справочной системы, снова нажмите клавишу <F1> и выбеY рите раздел, посвященный слову End, из библиотеки Excel. Свойство End возY вращает объект Range, что эквивалентно последовательному нажатию клаY виш <End> и <↑> или <End> и <↓> в пользовательском интерфейсе Excel. Щелкнув на ссылке XlDirection, можно увидеть список параметров, допусY тимых для передачи функции End (рис. 2.16). Знакомство с Visual Basic for Applications Глава 2 Рис. 2.15. Поиск нужного раздела справочной системы можно проводить методом проб и ошибок Возврат объектов свойством Ранее неоднократно упоминалось, что базовый синтаксис языка VBA предY ставлен конструкцией Объект.Метод. В рассмотренной выше строке кода меY тодом, очевидно, является метод .Select. Несмотря на то, что End — это свойство, оно возвращает объект Range, а метод, таким образом, применяется непосредственно к свойству. Открыв раздел справочной системы, посвященный слову Selection, можно обнаружить, что это также свойство, а не объект. Полное обращение к свойству Selection выглядит как Application.Selection, однако в конY тексте использования объектной модели Excel префикс Application можно опустить. Если бы данный макрос выполнялся в текстовом редакторе Word, нам обязательно потребовалось бы указать перед свойством .Selection пеY ременную объекта для идентификации вызываемого приложения. 73 74 Часть I Первые шаги Рис. 2.16. Нужный раздел справочной системы, посвященный свойству End Тип возвращаемого свойством Application.Selection объекта зависит от текущего выделенного элемента. Если это ячейка, свойство Application.Selection возвращает объект Range. Использование отладчика кода Редактор Visual Basic содержит великолепный отладчик, предназначенный для поиска и устранения недостатков программного кода. Пошаговое выполнение кода Обычно на выполнение макроса уходит всего лишь несколько секунд. Если во время этого произойдет какойYто сбой, отследить его будет очень трудно. К счастью, отладчик Excel поддерживает пошаговое выполнение кода. Разместите курсор посредине имени процедуры ИмпортСчета и выберите команду меню Debug Step Into (Отладка Пошаговое выполнение) (или наY жмите клавишу <F8>) (рис. 2.17). Сейчас редактор Visual Basic находится в режиме пошагового выполнеY ния кода. Строка, которая будет выполнена следующей, выделена желтым цветом. Кроме того, на нее указывает желтая стрелка, расположенная слеY ва (рис. 2.18). Знакомство с Visual Basic for Applications Глава 2 Рис. 2.17. Пошаговое выполнение кода позволяет обнаружить и устранить его недостатки Рис. 2.18. Отладчик готов выполнить первую строку кода макроса Выполнение строки Sub ИмпортСчета() приводит к входу в процедуру ИмпортСчета(). Нажмите клавишу <F8>, чтобы выполнить эту строку и пеY рейти к следующей. Редактор Visual Basic выделит желтым цветом фрагмент кода, соответствующий методу OpenText. Нажмите клавишу <F8>. После выполнения метода OpenText переключитесь в Excel с помощью комбинаY ции клавиш <Alt+Tab> и убедитесь в успешном импорте файла Счет.txt. Обратите внимание, что текущей выделенной ячейкой является ячейка A1 (рис. 2.19). 75 76 Часть I Первые шаги Рис. 2.19. Файл Счет.txt успешно импортирован в Excel Переключитесь в редактор Visual Basic, воспользовавшись комбинацией клавиш <Alt+Tab>. Нажмите клавишу <F8>, чтобы выполнить строку кода макроса Selection.End(xlDown).Select. Переключившись в Excel, можно увидеть, что теперь текущей выделенной ячейкой является ячейка A10 (рис. 2.20). Рис. 2.20. Выполнение команды Selection.End(xlDown).Select эквивалентно последовательному нажатию клавиш <End> и <↓> Переключившись в редактор Visual Basic, нажмите клавишу <F8>, чтобы выполнить команду Range("A14").Select. Вместо того чтобы выделить ячейку в первой свободной строке после импортированных данных (A11), макрос выделил ячейку A14, как показано на рис. 2.21. Рис. 2.21. Записанный макрос допускает ошибку Знакомство с Visual Basic for Applications Глава 2 Обнаружив проблемный участок кода, остановите выполнение макроса, выбрав команду меню Run Reset (Выполнить Сброс) или щелкнув на кнопке панели инструментов Reset (Сброс) (рис. 2.22). Вернитесь в Excel и отмените все действия, которые успел выполнить макрос. В данном случае заY кройте файл Счет.txt без сохранения изменений. Рис. 2.22. Щелчок на кнопке Reset приводит к остановке выполнения макроса Точки прерывания Длина некоторых макросов может достигать сотен строк. Чтобы добраться к проблемному участку кода, совсем необязательно пошагово выполнять все предшествующие ему строки. Создайте точку прерывания, и выполнение макY роса будет остановлено на ее границе. Чтобы создать точку прерывания, щелкните на полосе слева от строки коY да, перед выполнением которой необходимо сделать остановку. Строка кода будет выделена красноYкоричневым цветом, а слева от нее появится такого же цвета маркер (рис. 2.23). Рис. 2.23. Красно&коричневый маркер слева от строки кода свидетельствует о наличии точки прерывания Выберите команду Run Run Sub/UserForm (Выполнить Выполнить подY программу/Пользовательскую форму) или нажмите клавишу <F5>. ВыполнеY ние макроса остановится на границе точки прерывания, а соответствующая 77 78 Часть I Первые шаги строка кода будет выделена желтым цветом. Нажмите клавишу <F8>, чтобы продолжить выполнение макроса в пошаговом режиме (рис. 2.24). Рис. 2.24. Строка кода, на которой установлена точка прерывания, выделена желтым цветом Завершив отладку кода, следует удалить все точки прерывания. Чтобы удаY лить точку прерывания, щелкните на соответствующей ей точке на полосе слева от строки кода. Чтобы удалить все точки прерывания в проекте, выбериY те команду меню Debug Clear All Breakpoints (Отладка Удалить все точки прерывания) или воспользуйтесь комбинацией клавиш <Ctrl+Shift+F9>. Перемещение по коду Пошаговый режим отладки позволяет изменить порядок выполнения строк кода. Чтобы пропустить фрагмент кода или вернуться к уже выполнявшимся строкам, перетащите желтую стрелку, расположенную на полосе слева от кода. При подведении указателя мыши к стрелке он меняет свою форму, как показано на рис. 2.25. Перетащите желтую стрелку на строку кода, которая должна быть выполнена следующей, или разместите на этой строке курсор и выберите коY манду меню Debug Set Next Statement (Отладка Выполнить следующей). Рис. 2.25. При подведении указателя мыши к желтой стрелке он меняет свою форму Знакомство с Visual Basic for Applications Глава 2 Выполнение фрагмента кода Иногда возникает необходимость в выполнении целого фрагмента кода, наY пример, цикла. Вместо того чтобы возвращаться к одним и тем же строкам неY сколько раз подряд, можно указать отладчику на необходимость выполнения всего участка кода до указанной вами строки. Для этого разместите курсор на требуемой строке и воспользуйтесь комбинацией клавиш <Ctrl+F8> или команY дой меню Debug Run To Cursor (Отладка Выполнить до указанной строки). Вычисление значения переменной или выражения В режиме пошагового выполнения кода можно просмотреть значение пеY ременной или выражения (между прочим, средство записи кода никогда не создает переменных). Окно Immediate Чтобы открыть окно Immediate (Быстрое выполнение) в редакторе Visual Basic, нажмите комбинацию клавиш <Ctrl+G>. На рис. 2.26 приведен пример вычисления различных выражений, таких как адрес текущей выделенной ячейки, ее значение, а также имя активного рабочего листа. Окно Immediate обычно располагается под окном просмотра программY ного кода. Размер окна Immediate можно изменить, воспользовавшись маркеY ром изменения размера окна (рис. 2.27). Рис. 2.26. Пауза после выполнения каждой строки кода позволяет узнать текущие значе& ния переменных или выражений Рис. 2.27. Изменение размера окна Immediate Если содержимое окна Immediate не умещается на экране, его можно проY смотреть с помощью полосы прокрутки, расположенной в правой части окна. Выражение, значение которого необходимо вычислить с помощью окна Immediate, не обязательно набирать каждый раз заново. К примеру, вычислим значение выражения Selection.Address после выполнения нескольких строк кода макроса (рис. 2.28). Нажмите клавишу <F8>, чтобы выполнить следующую строку кода. ВмеY сто повторного ввода выражения, установите курсор в конец содержащей это выражение строки (рис. 2.29). 79 80 Часть I Первые шаги Рис. 2.28. Вычисление значения выра& жения в окне Immediate Рис. 2.29. Чтобы повторно вычис& лить результат выражения, устано& вите курсор в конец содержащей это выражение строки и нажмите кла& вишу <Enter> Чтобы повторно вычислить результат выражения, нажмите клавишу <Enter>. Новый результат (в данном случае $1:$1) ‘‘сдвинет’’ старый ($E$14:$G$14) на одну строку вниз (рис. 2.30). Нажмите клавишу <F8> четыре раза, чтобы выполнить строку Cells.Select. Снова расположите курсор в конце строки Print Selection.Address в окне Immediate и нажмите клавишу <Enter>. Новый реY зультат выражения Selection.Address сдвинет на одну строку вниз два предыдущих (рис. 2.31). Рис. 2.30. Старый результат выра& жения был сдвинут новым резуль& татом на одну строку вниз Рис. 2.31. После выделения всех ячеек текущий адрес выбранного диапазона изменился на $1:$65536 Выражение, указанное в окне Immediate, можно изменить. Установите курсор справа от слова Address и удалите его с помощью клавиши <Backspace>. Введите выражение Rows.Count и нажмите клавишу <Enter>. В окне Immediate появится значение, равное числу выделенных строк (рис. 2.32). Изменение выражения в окне Immediate часто применяется при отладке проблемных участков кода. В подобных ситуациях может пригодиться самая различная информация YYYY имя активного рабочего листа (Print Active- Знакомство с Visual Basic for Applications Глава 2 sheet.Name), адрес выбранного диапазона ячеек (Print Selection. Address), адрес активной ячейки (Print ActiveCell.Address), формула активной ячейки (Print ActiveCell.Formula), значение активной ячейки (Print ActiveCell.Value или же просто Print ActiveCell, так как Value является стандартным свойством ячейки) и т.д. Рис. 2.32. Измените выражение, ука& занное в окне Immediate, и нажмите клавишу <Enter> Вычисление значения с помощью указателя мыши Чтобы узнать значение выражения, подведите к нему указатель мыши и заY держите в таком положении пару секунд. На экране появится подсказка, соY держащая текущее значение выражения. Как правило, этот прием оказываетY ся наиболее полезным при отладке циклов (см. главу 5, ‘‘Циклы и управление выполнением кода’’). Пригодится он и при работе с автоматически сгенериY рованным кодом. Заметьте, что выражение, значение которого вычисляется описанным выше способом, не обязано содержаться в только что выполненY ной строке кода. Как показано на рис. 2.33, макрос только выделил все ячейки (при этом текущей активной ячейкой является ячейка A1). Подведя указатель мыши к выражению ActiveCell.FormulaR1C1, можно узнать, что его знаY чением является строка СчетДата. Рис. 2.33. Чтобы узнать значение выражения, задержите над ним указатель мыши 81 82 Часть I Первые шаги Иногда окно просмотра кода редактора Visual Basic не реагирует на указаY тель мыши. Поскольку некоторые выражения не имеют значения, назвать причину отсутствия подсказки удается не сразу. Подведите указатель мыши к выражению, которое всегда должно иметь значение, например, к переменной. При отсутствии подсказки щелкните на имени переменной и задержите над ним указатель мыши до появления подсказки. Как показывает практика, это всегда выводит редактор Visual Basic из состояния ступора. Вам все еще не нравится Visual Basic? Бьюсь об заклад, что после знакомства с его рабочей средой вы настроены гораздо менее категорично. Эти средства отладки просто потрясающи! Окно Watches Окно Watches (Просмотр) позволяет отслеживать значение любого выраY жения во время выполнения кода. Отследим текущий адрес выделенного диаY пазона ячеек (Selection.Address). Выберите команду меню редактора Visual Basic Debug Add Watch (Отладка Добавить в окно просмотра). Введите Selection.Address в текстовом поле Expression (Выражение) диалогового окна Add Watch (Добавить в окно просмотра) и щелкните на кнопке OK (рис. 2.34). Окно Watches обычно располагается под окном просмотра программного кода. Запустите макрос ИмпортСчета в режиме пошагового выполнения и остановитесь перед строкой Range("A14").Select. Текущее значение выY ражения Selection.Address будет равно $A$10 (рис. 2.35). Рис. 2.34. Добавление в окно просмотра теку& щего адреса выделенного диапазона ячеек Рис. 2.35. Окно Watches позволяет от& слеживать текущее значение выраже& ния на протяжении всего времени вы& полнения кода Нажмите клавишу <F8>, чтобы выполнить строку Range("A14").Select. В окне Watches будет отображен новый адрес выделенного диапазона ячеек YYYY $A$14 (рис. 2.36). Знакомство с Visual Basic for Applications Глава 2 Рис. 2.36. Содержимое окна Watches обновляется после вы& полнения каждой строки кода Установка точки прерывания с помощью окна Watches Щелкните правой кнопкой мыши на значке с изображением очков в окне Watches (Просмотр) и выберите команду контекстного меню Edit Watch (Изменить параметры просмотра). Установите переключатель Break When Value Changes (Приостановить при изменении значения) в группе переклюY чателей Watch Type (Способ просмотра) диалогового окна Edit Watch (Изменить параметры просмотра) (рис. 2.37). Щелкните на кнопке OK. Рис. 2.37. Установите When Value Changes переключатель Break Значок с изображением очков сменится на значок с изображением руки и треугольника. Нажмите клавишу <F5> для выполнения макроса. Как только значение выделенного диапазона ячеек изменится, выполнение макроса будет приостановлено. Данная возможность является чрезвычайно полезной при отладке кода. Отслеживание состояния объекта с помощью окна Watches Ранее было рассмотрено отслеживание значения свойства Selection. Address. Редактор Visual Basic позволяет также следить за состоянием целых объектов, таких как объект Selection (рис. 2.38). 83 84 Часть I Первые шаги Рис. 2.38. При отслеживании состояния объекта рядом со значком с изображением очков появ& ляется значок с изображением знака “плюс” Щелкните на значке с изображением знака ‘‘плюс’’, чтобы просмотреть все свойства объекта Selection (рис. 2.39). Существование некоторых из них окажется для вас настоящим сюрпризом. Кроме новых свойств наподобие .AddIndent (значение False) и .AllowEdit (значение True), вы увидите также уже знакомые свойства, такие как .Formula. Рис. 2.39. Щелкните на значке с изображением знака “плюс”, чтобы просмотреть список свойств объекта и их текущих значений Возле некоторых свойств объекта Selection, таких как коллекция Borders, находится значок с изображением знака ‘‘плюс’’. Щелкните на нем, чтобы получить более детальную информацию об объекте. Знакомство с Visual Basic for Applications Глава 2 Диспетчер объектов Чтобы открыть окно диспетчера объектов редактора Visual Basic, нажмите клавишу <F2> (рис. 2.40). Рис. 2.40. Чтобы открыть окно диспетчера объектов, нажмите клавишу <F2> Диспетчер объектов позволяет просматривать библиотеку объектов Excel и проводить поиск в ней. Распечатка списка всех объектов из этой библиотеки занимает порядка 409 страниц текста, однако благодаря диспетчеру объектов работать с библиотекой совсем нетрудно. Окно диспетчера объектов занимает пространство окна просмотра проY граммного кода. С помощью верхнего раскрывающегося списка можно выY брать все подключенные библиотеки (All Libraries (Все библиотеки)), библиоY теку Excel, Office, VBA, библиотеку каждой открытой рабочей книги, а также все остальные библиотеки, указанные с помощью диалогового окна References (Ссылки) (чтобы открыть диалоговое окно References, выберите команду меню редактора Visual Basic Tools References (Сервис Ссылки)). Раскройте список и выберите библиотеку Excel. В левой части окна диспетчера объектов содержится список классов бибY лиотеки Excel. Щелкните на имени класса Application. В правой части окY на диспетчера объектов появится список свойств и методов объекта Application (рис. 2.41). 85 86 Часть I Первые шаги Рис. 2.41. Выберите класс, а затем — метод или свойство. В нижней части окна диспетчера объектов появится краткое описание выбранного элемента. Рядом с именем метода в правой части окна диспетчера объектов находится значок с изображением зеленой книги, а рядом с именем свойства — изображение учетной карточки с указывающей на нее кистью руки Щелкните на имени свойства ActiveCell. В нижней части окна диспетчера объектов появится краткое описание свойства ActiveCell, из которого можно узнать тип возвращаемого этим свойством значения YYYY Range. Кроме того, свойство ActiveCell предназначено только для чтения, что делает невозможY ным присвоение ему значения с целью сдвинуть указатель активной ячейки. Щелкните на ссылке Range в нижней части окна диспетчера объектов, чтобы увидеть список свойств и методов объекта Range, а значит и свойства ActiveCell. Щелкните на имени любого свойства или метода объекта Range, а затем YYYY на кнопке с изображением желтого вопросительного знака в верхней части диспетчера объектов. В результате откроется окно справочной системы с разделом, посвященным выбранному элементу. Введите любое ключевое слово в поле ввода раскрывающегося списка, наY ходящегося справа от кнопки с изображением бинокля, и щелкните на этой кнопке, чтобы найти все подходящие под данное ключевое слово элементы библиотеки Excel. Чтобы закрыть окно диспетчера объектов и вернуться к окну просмотра программного кода, щелкните на кнопке с изображением крестика в верхнем правом углу окна диспетчера объектов (рис. 2.42). Знакомство с Visual Basic for Applications Глава 2 Рис. 2.42. Чтобы закрыть окно диспетчера объектов, щелкните на кнопке с изображением кре& стика в верхнем правом углу окна 5 советов по исправлению и оптимизации автоматически сгенерированного кода Приблизившись к концу второй главы, было бы неплохо исправить хотя бы один из двух имеющихся у нас проблемных макросов. Ниже приведено 5 советов, направленных на оптимизацию и исправление автоматически сгеY нерированного кода. Совет 1: ничего не выделяйте Отличительной особенностью автоматически сгенерированного кода являY ется выделение элементов перед их дальнейшим использованием. В некотоY ром смысле это подразумевает копирование действий, совершаемых с помоY щью пользовательского интерфейса Excel. Так, чтобы сделать текст ячейки утолщенным, ее необходимо сначала выделить. Подобная практика является совершенно излишней в VBA. (Существуют исключения, которые, однако же, обусловлены не вполне корректным повеY дением некоторых методов, требующих для своего выполнения предварительY ного выделения объекта диаграммы.) Чтобы сделать текст ячейки утолщенY 87 88 Часть I Первые шаги ным, последнюю можно и не выделять. Ниже показан пример преобразования двух строк автоматически сгенерированного кода макроса в одну. Автоматически сгенерированный код: Rows("1:1").Select Selection.Font.Bold = True Оптимизированный код: Rows("1:1").Font.Bold = True Подобное преобразование имеет несколько преимуществ. ВоYпервых, коY личество строк кода уменьшается почти что вдвое. ВоYвторых, код выполняетY ся быстрее. Чтобы оптимизировать приведенный выше фрагмент кода, выделите фрагмент Select в верхней строке кода и фрагмент Selection. — в нижY ней, после чего щелкните на кнопке <Delete> (рис. 2.43 и 2.44). Рис. 2.43. Выделите фрагмент ко& да так, как показано на рисунке... Рис. 2.44. ...и нажмите клавишу <Delete> Совет 2: перемещайтесь на последнюю строку данных с конца рабочего листа Никогда не доверяйте данным, поступившим из внешних источников. РаY но или поздно вы столкнетесь с содержащимися в них ошибками, например, с отсутствием номера счета. Вне зависимости от причины ошибок (сбой в электропитании или человеческий фактор), следует запомнить одно — нет никаких оснований полагать, что все ячейки содержат данные. С учетом сказанного выше, последовательное нажатие клавиш <End> и <↓> приводит не к перемещению на последнюю строку данных, а к перемеY щению на последнюю строку данных в определенном диапазоне ячеек. К примеру, на рис. 2.45 последовательное нажатие клавиш <End> и <↓> приY ведет к перемещению в ячейку A6, а не в ячейку A10. Рис. 2.45. Последовательное нажатие клавиш <End> и <↓> (выражение End(xlDown) в VBA) срабатывает некорректно при отсутствии значения в ячейке Знакомство с Visual Basic for Applications Глава 2 Одним из возможных решений этой проблемы является перемещение в конец рабочего листа Excel и последовательное нажатие клавиш <End> и <↑>. В контексте пользовательского интерфейса Excel подобная процедура не имеY ет смысла, однако она способна помочь макросу VBA переместиться на нужY ную строку: Range("A65536").End(xlUp) Внимание Начиная с Excel 97 максимальное количество строк в Excel равно 65 536 (ранее оно равнялось 16 384). Чтобы обеспечить совместимость кода макроса с любой вер& сией Excel, жестко закодированное значение 65 535 рекомендуется заменить вы& ражением Rows.Count (максимальное число строк в текущей версии Excel). Строка Cells(Row.Count, 1).End(xlUp) гарантирует правильность работы макроса как в будущих, так и в предыдущих версиях Excel. Совет 3: используйте переменные Средство записи макросов никогда не создает переменные. О переменных речь пойдет далее в этой книге, а пока что можно отметить, что, как и в BASIC, переменные используются для хранения значений. Создадим переменную для хранения номера последней строки данных. ПереY менным рекомендуется давать информативные имена, например, FinalRow. FinalRow = Range("A65536").End(xlUp).Row Зная номер последней строки данных, разместить в столбце A следующей строки слово ‘‘Всего’’ можно с помощью такого кода: Range("A" & FinalRow + 1).Value = "Всего" См. также Более простой способ обращения к этой ячейке рассматривается в разделе “Обращение к диапазону ячеек с помощью свойства Cells” главы 3 на с. 99. Переменные можно использовать и при построении формулы. К примеру, приведенная ниже формула суммирует все значения, начиная с ячейки E2 и заканчивая ячейкой, находящейся на пересечении последней строки данных и столбца E: Range("E" & FinalRow + 1).Formula = "=SUM(E2:E" & FinalRow & ")" Совет 4: используйте одно выражение для копирования и вставки данных Автоматически сгенерированный код ‘‘славится’’ своей четырехшаговой процедурой копирования и вставки данных, подразумевающей выделение исY ходного диапазона ячеек, его копирование, выделение целевого диапазона 89 90 Часть I Первые шаги ячеек и, наконец, вызов метода ActiveSheet.Paste. Метод Copy, примеY няемый к диапазону ячеек, обладает намного более широкой функциональноY стью, позволяя задать источник и назначение копируемых данных с помощью одного выражения. Ниже приведен фрагмент автоматически сгенерированного кода: Range("E14").Select Selection.Copy Range("F14:G14").Select ActiveSheet.Paste А это YYYY тот же код после оптимизации: Range("E14").Copy Destination:=Range("F14:G14") Совет 5: используйте конструкцию With...End With Ниже приведен автоматически сгенерированный код, изменяющий разY личные параметры шрифта выделенного диапазона ячеек: Range("A14:G14").Select Selection.Font.Bold = True Selection.Font.Size = 12 Selection.Font.ColorIndex = 5 Selection.Font.Underline = xlUnderlineStyleDoubleAccounting При выполнении этого кода макрос должен 4 раза подряд вычислить знаY чение выражения Selection.Font. Поскольку каждый раз обращение проY исходит к одному и тому же объекту, его имя рекомендуется указать в начале блока With. Чтобы сослаться на объект внутри блока With, соответствующие строки кода необходимо предварить символом точки, как показано ниже: With Range("A14:G14").Font .Bold = True .Size = 12 .ColorIndex = 5 .Underline = xlUnderlineStyleDoubleAccounting End With Исправление и оптимизация автоматически сгенерированного кода Практикум Изменение автоматически сгенерированного кода Используя приведенные выше советы, превратим автоматически сгенерированный код макроса ИмпортСчета (см. ниже) в эффективный и профессиональный код. Sub ИмпортСчета() ' ' ИмпортСчета Макрос ' Макрос записан 03.01.2005 (Александр Журавлев) Знакомство с Visual Basic for Applications Глава 2 ' ' Сочетание клавиш: Ctrl+и ' Workbooks.OpenText Filename:= _ "C:\Счет.txt", Origin:=1251, StartRow:=1, _ DataType:=xlDelimited, TextQualifier:= xlDoubleQuote, _ ConsecutiveDelimiter:=False, Tab:=False, Semicolon:= _ False, Comma:=True, Space:=False, Other:=False, _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 1), _ Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1)), _ TrailingMinusNumbers:=True Selection.End(xlDown).Select Range("A14").Select ActiveCell.FormulaR1C1 = "Всего" Range("E14").Select Selection.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)" Selection.AutoFill Destination:=Range("E14:G14"), _ Type:=xlFillDefault Range("E14:G14").Select Rows("1:1").Select Selection.Font.Bold = True Rows("14:14").Select Selection.Font.Bold = True Cells.Select Selection.Columns.AutoFit End Sub Чтобы исправить и оптимизировать код макроса, выполните следующие действия. 1. Оставьте метод Workbook.OpenText без изменений. 2. В следующей строке кода осуществляется попытка перейти на последнюю стро& ку с данными: Selection.End(xlDown).Select Ничего не выделяйте. Кроме того, создайте две переменные — для номера по& следней строки с данными и для номера итоговой строки. Чтобы избежать про& блемы пустой ячейки, переместитесь на последнюю строку с данными с конца рабочего листа: ' Найти последнюю строку с данными FinalRow = Range("A65536").End(xlUp).Row TotalRow = FinalRow + 1 3. Следующие строки кода соответствуют вводу слова “Всего” в столбец A итого& вой строки: Range("A14").Select ActiveCell.FormulaR1C1 = "Всего" Воспользуйтесь созданной ранее переменной TotalRow и откажитесь от выде& ления ячейки, как показано ниже: ' Создание итоговой строки Range("A" & TotalRow).Value = "Всего" 4. Приведенные ниже строки кода описывают ввод формулы суммы в столбец E и ее копирование в столбцы F и G: Range("E14").Select Selection.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)" Selection.AutoFill Destination:=Range("E14:G14"), _ 91 92 Часть I Первые шаги Type:=xlFillDefault Range("E14:G14").Select Вы уже наверное догадались, что выделять здесь абсолютно нечего. Приведен& ный ниже код помещает формулу суммы в требуемые ячейки итоговой строки (формат ссылок R1C1 рассматривается в главе 6, “Стиль записи ссылок R1C1”): Range("E" & TotalRow).Resize(1, 3).FormulaR1C1 = _ "=SUM(R2C:R[-1]C)" 5. Ниже приведен код, сгенерированный средством записи макросов при фор& матировании строки заголовков столбцов и итоговой строки: Rows("1:1").Select Selection.Font.Bold = True Rows("14:14").Select Selection.Font.Bold = True А вот и его оптимизированная версия: Rows("1:1").Font.Bold = True Rows(TotalRow & ":" & TotalRow).Font.Bold = True 6. Перед вызовом метода AutoFit средство записи макросов выделяет все ячей& ки рабочего листа: Cells.Select Selection.Columns.AutoFit Как вы уже догадались, это совершенно излишне: Cells.Columns.AutoFit 7. Ниже приведен комментарий, добавляемый к каждому макросу при его создании: ' ИмпортСчета Макрос ' Макрос записан 03.01.2005 (Александр Журавлев) ' ' Сочетание клавиш: Ctrl+и Исправив и оптимизировав автоматически сгенерированный код, вы имеете полное право заменить слово “записан” на “создан”, как показано ниже: ' ИмпортСчета Макрос ' Макрос создан 03.01.2005 (Александр Журавлев) ' ' Сочетание клавиш: Ctrl+и Ниже приведен полный код исправленного и оптимизированного макроса. Sub ИмпортСчетаИсправленный () ' ' ИмпортСчета Макрос ' Макрос создан 03.01.2005 (Александр Журавлев) ' ' Сочетание клавиш: Ctrl+и ' Workbooks.OpenText Filename:= _ "C:\Счет.txt", Origin:=1251, StartRow:=1, _ DataType:=xlDelimited, TextQualifier:= xlDoubleQuote, _ ConsecutiveDelimiter:=False, Tab:=False, Semicolon:= _ False, Comma:=True, Space:=False, Other:=False, _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 1), _ Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1)), _ TrailingMinusNumbers:=True ' Найти последнюю строку с данными Знакомство с Visual Basic for Applications Глава 2 FinalRow = Range("A65536").End(xlUp).Row TotalRow = FinalRow + 1 ' Создание итоговой строки Range("A" & TotalRow).Value = "Всего" Range("E" & TotalRow).Resize(1, 3).FormulaR1C1 = _ "=SUM(R2C:R[-1]C)" Rows("1:1").Font.Bold = True Rows(TotalRow & ":" & TotalRow).Font.Bold = True Cells.Columns.AutoFit End Sub Следующий шаг В этой главе были рассмотрены основы синтаксиса языка программироваY ния Visual Basic for Applications, использование справочной системы, средств отладки, а также несколько советов по исправлению и оптимизации автомаY тически сгенерированного кода. Следующая глава посвящена более детальному изучению диапазонов ячеек. 93 Глава 3 Ðàáîòà ñ äèàïàçîíîì ÿ÷ååê Диапазон ячеек представляет соY бой любое их объединение в пределах одного рабочего листа. Примерами диапазона ячеек являются ячейка, строка, столбец и т.п. Объект Range YYYY один из наиболее популярY ных объектов Excel VBA. В этой главе будут рассмотрены различные способы обращения к диаY пазону ячеек в пределах как одного, так и нескольких рабочих листов, объедиY нение диапазонов ячеек, а также создаY ние нового диапазона ячеек из неY скольких пересекающихся диапазонов. Объект Range Рассмотрим следующую иерархию объектов Excel: Application Workbook Worksheet Range Объект Range является свойством объекта Worksheet. Обе приведенY ные ниже строки кода выполняют одно и то же действие, если рабочий лист Worksheets(1) является акY тивным рабочим листом. Range("A1") Worksheets(1).Range("A1") Таким образом, существует неY сколько способов обращения к диаY пазону ячеек. Range("A1") является наиболее распространенным из них в основном за счет того, что это станY дартный способ обращения к диапаY зону ячеек, использующийся средстY 3 Объект Range ..................................95 Обращение к диапазону ячеек с помощью указания адреса его верхнего левого и нижнего правого угла ...................96 Обращение к диапазону ячеек, расположенному на другом рабочем листе ..................97 Обращение к диапазону ячеек с помощью указания его относительного адреса ................ 98 Обращение к диапазону ячеек с помощью свойства Cells.............99 Обращение к диапазону ячеек с помощью свойства Offset........ 100 Изменение размера диапазона ячеек с помощью свойства Resize ..............................101 Обращение к диапазону ячеек с помощью свойств Columns и Rows ................................................ 102 Объединение диапазонов ячеек с помощью метода Union................................................ 103 Создание нового диапазона ячеек из пересекающихся диапазонов с помощью метода Intersect ............................ 103 Проверка пустых ячеек с помощью функции IsEmpty .......104 Обращение к диапазону ячеек с помощью свойства CurrentRegion ................................ 105 Обращение к диапазону несмежных ячеек с помощью коллекции Areas...........................108 Следующий шаг............................ 109 96 Часть I Первые шаги вом записи макросов. Приведенные ниже строки кода полностью эквиваY лентны: Range("D5") [D5] Range("B3").Range("C3") Cells(5, 4) Range("A1").Offset(4, 3) Range("МойДиапазон") 'при условии что МойДиапазон - имя ячейки D5 Более подробно различные способы обращения к диапазону ячеек расY сматриваются далее в этой главе. Обращение к диапазону ячеек с помощью указания адреса его верхнего левого и нижнего правого угла Существует два различных синтаксиса команды Range. Согласно первому из них обращение к диапазону ячеек осуществляется путем указания его полY ного адреса, как это принято в формулах Excel: Range("A1:B5").Select Согласно второму синтаксису обращение к диапазону ячеек осуществляетY ся путем указания адреса его верхнего левого и нижнего правого угла, как поY казано ниже: Range("A1", "B5").Select Вместо адреса любого из углов можно подставить имя диапазона ячеек, функцию Cells, а также свойство ActiveCell. В следующей строке кода осуществляется выделение прямоугольного диапазона ячеек, в верхнем левом углу которого находится ячейка A1, а в нижнем правом углу YYYY активная ячейка: Range("A1", ActiveCell).Select А вот как выделить диапазон ячеек, в верхнем левом углу которого нахоY дится активная ячейка, а в нижнем правом углу YYYY ячейка, находящаяся на 5 строк ниже и на 2 столбца правее активной ячейки: Range(ActiveCell, ActiveCell.Offset(5, 2)).Select Сокращенная форма обращения к диапазону ячеек Сокращенная форма обращения к диапазону ячеек предполагает использоY вание квадратных скобок ([]), как показано в табл. 3.1. Именованные диапазоны ячеек Именованные диапазоны ячеек можно использовать не только на рабочих листах и в формулах Excel, но также и в VBA. Ниже приведен пример обращения к именованному диапазону ячеек МойДиапазон на рабочем листе Лист1: Worksheets("Лист1").Range("МойДиапазон").Select Работа с диапазоном ячеек Глава 3 Обратите внимание, что имя диапазона ячеек взято в кавычки. Это отличиY тельная особенность использования именованных диапазонов в VBA. Без каY вычек Excel воспримет имя диапазона ячеек как объявленную в макросе переY менную. Единственное исключение касается сокращенной формы обращения к диапазону ячеек, которая не предусматривает заключение имени диапазона в кавычки. Таблица 3.1. Сокращенная форма обращения к диапазону ячеек Стандартная форма Сокращенная форма Range("D5") [D5] Range("A1:D5") [A1:D5] Range("A1:D5", "G6:I17") [A1:D5, G6:I17] Range("МойДиапазон") [МойДиапазон] Обращение к диапазону ячеек, расположенному на другом рабочем листе Переключение между рабочими листами может существенно замедлить выполнение кода макроса. Чтобы избежать этого, можно обратиться непоY средственно к объекту Worksheet, как показано ниже: Worksheets("Лист1").Range("A1") В приведенном выше коде происходит обращение к рабочему листу Лист1, даже если активным рабочим листом на данный момент является лист Лист2. Чтобы обратиться к диапазону ячеек в другой рабочей книге, воспользуйY тесь объектами Workbook, Worksheet и Range, как показано ниже: Workbooks("Счета.xls").Worksheets("Лист1").Range("A1") Будьте внимательны, используя свойство Range в качестве аргумента друY гого свойства Range. В подобных случаях необходима полная идентификация диапазона ячеек. Предположим, что активным рабочим листом является лист Лист1, а суммирование данных производится на листе Лист2 так, как покаY зано ниже: WorksheetFunction.Sum(Worksheets("Лист2").Range(Range("A1"), _ Range("A7"))) Приведенная выше строка кода не будет выполняться, поскольку Excel не распространит ссылку на объект Worksheet на вложенные объекты Range. Чтобы исправить ситуацию, можно поступить так: WorksheetFunction.Sum(Worksheets("Лист2").Range(Worksheets( _ "Лист2").Range("A1"), Worksheets("Лист2").Range("A7"))) Однако еще лучше упростить эту достаточно длинную строку кода с помощью конструкции With...End, позволяющей заменить выражения 97 98 Часть I Первые шаги Worksheets("Лист2").Range более короткой формой .Range, как поY казано ниже: With Worksheets("Лист2") WorksheetFunction.Sum(.Range(.Range("A1"), .Range("A7"))) End With Обращение к диапазону ячеек с помощью указания его относительного адреса Обычно объект Range выступает в качестве свойства рабочего листа. ВмеY сте с тем, он может быть свойством другого объекта Range, внося неразбериху в и без того непростой программный код. Рассмотрим пример: Range("B5").Range("C3").Select В результате выполнения приведенного выше кода выделяется ячейка D7. Чтобы понять, почему так происходит, рассмотрим ячейку C3. Ячейка C3 расY положена на две строки ниже и на два столбца правее ячейки A1. Однако в указанном выше коде точкой отсчета является ячейка B5. Другими словами, VBA выделит ячейку, которая находится на том же смещении относительно ячейки B5, что и ячейка C3 относительно ячейки A1 (на две строки ниже и на два столбца правее), а именно D7. Подобный стиль записи программного кода является весьма неинтуитивY ным. На первый взгляд указанные в строке кода адреса ячеек не имеют ни маY лейшего отношения к адресу выделяемой ячейки! Тем не менее, данный синтаксис может пригодиться при обращении к ячейке, расположенной на определенном смещении относительно активной ячейки. К примеру, в результате выполнения приведенной ниже строки кода выделяется ячейка, расположенная на 3 строки ниже и на 4 столбца правее теY кущей активной ячейки: Selection.Range("E4").Select Аналогичного результата (с применением куда более понятного синтаксиY са) можно добиться путем использования свойства Offset, которое рассматY ривается далее в этой главе. Зачем же нужно знать о существовании такого неудобного способа обраY щения к диапазону ячеек? Дело в том, что именно он пришелся ‘‘по душе’’ средству записи макросов. Ниже приведена одна из строк кода, сгенерированY ных при записи макроса импорта счета с использованием относительных ссыY лок (см. главу 1, ‘‘Excel и VBA YYYY гремучая смесь’’): ActiveCell.Offset(0, 4).Range("A1").Select Выполнение этого кода приведет к выделению ячейки, соответствующей ячейке A1 с учетом смещения относительно активной ячейки на 4 столбца вправо. Работа с диапазоном ячеек Глава 3 Обращение к диапазону ячеек с помощью свойства Cells Свойство Cells используется для обращения ко всем ячейкам объекта Range, будь то целый рабочий лист или определенный диапазон ячеек. К примеру, результатом выполнения приведенной ниже строки кода является выделение всех ячеек активного рабочего листа: Cells.Select Использование свойства Cells вместе с объектом Range выглядит избыY точным: Range("A1:D5").Cells Что делает объект Cells действительно полезным, так это его свойство Item, которое позволяет обратиться к любой ячейке диапазона. Ниже приведен синтаксис использования свойства Item с объектом Cells: Cells.Item(Строка, Столбец) Идентификацию строки разрешается проводить только с помощью числоY вого значения, а идентификацию столбца YYYY с помощью числового или строY кового значения. В обеих приведенных ниже строках кода осуществляется обY ращение к ячейке C5: Cells.Item(5, "C") Cells.Item(5, 3) Поскольку свойство Item является свойством по умолчанию объекта Range, справедлива следующая сокращенная запись: Cells(5, "C") Cells(5, 3) Возможность использования числовых значений при указании параметров будет по достоинству оценена при создании циклов. Для выделения ячейки средство записи макросов применяет выражение наподобие Range("A1") .Select, а для выделения диапазона ячеек YYYY Range("A1:C5").Select. Следующие строки выдержаны в стиле автоматически сгенерированного кода: FinalRow = Range("A65536").End(xlUp).Row For i = 1 To FinalRow Range("A" & i & ":E" & i).Font.Bold = True Next i В результате использования ‘‘недружелюбного’’ синтаксиса цикл, выдеY ляющий ячейки в столбцах A–E с помощью утолщения шрифта, оказался весьма сложным для восприятия. Попробуем записать его несколько иначе: FinalRow = Cells(65536, 1).End(xlUp).Row For i = 1 To FinalRow Cells(i, "A").Resize(, 5).Font.Bold = True Next i Использование свойств Cells и Resize вместо адреса диапазона ячеек делает код цикла более наглядным. 99 100 Часть I Первые шаги Использование свойства Cells в качестве параметра свойства Range Свойство Cells можно использовать в качестве параметра свойства Range. Приведенная ниже строка кода описывает диапазон ячеек A1:E5: Range(Cells(1, 1), Cells(5, 5)) Применение подобного подхода оправдано в случае необходимости исY пользования переменных, как в предыдущем примере кода цикла. Обращение к диапазону ячеек с помощью свойства Offset Свойство Offset используется средством записи макросов при генерироY вании кода в режиме относительных ссылок. Это свойство позволяет обраY щаться к ячейке с помощью относительного адреса, отсчитываемого от адреса активной ячейки. Ниже приведен синтаксис использования свойства Offset: Range.Offset(СмещениеПоСтрокам, СмещениеПоСтолбцам ) Чтобы обратиться к ячейке F5 при условии, что текущей активной ячейкой является ячейка A1, используйте выражение Range("A1").Offset(RowOffset:=4, ColumnOffset:=5) или его сокращенную форму Range("A1").Offset(4, 5) Отсчет адресов ячеек ведется с адреса ячейки A1. Сама ячейка A1 при этом не учитывается. Одна из замечательных особенностей свойства Offset заключается в отY сутствии необходимости указывать оба параметра одновременно. Чтобы обраY титься к ячейке, расположенной на один столбец правее ячейки A1, испольY зуйте любое из следующих выражений: Range("A1").Offset(ColumnOffset:=1) Range("A1").Offset(, 1) А вот как обратиться к ячейке, расположенной на одну строку выше ячейки B2: Range("B2").Offset(RowOffset:=-1) Range("B2").Offset(-1) Рассмотрим таблицу с двумя столбцами, в одном из которых перечислены проY дукты питания, а в другом YYYY их запасы. Чтобы найти продукт, запасы которого подошли к концу, и отметить это путем размещения в следующей ячейке слова ‘‘ПОПОЛНИТЬ’’, можно воспользоваться следующим макросом: Set Rng = Range("B1:B16").Find(What:="0", LookAt:=xlWhole, _ LookIn:=xlValues) Rng.Offset(, 1).Value = "ПОПОЛНИТЬ" Работа с диапазоном ячеек Глава 3 Результат выполнения макроса показан на рис. 3.1. Свойство Offset позволяет смещать не только отдельные ячейки, но даже целые диапазоны. Приведенная ниже строка кода смещает диапазон ячеек A1:C3 на одну строку вниз и на один столбец правее так, что он переходит в диапазон ячеек B2:D4 (рис. 3.2). Range("A1:C3").Offset(1, 1) Рис. 3.1. Результат выполнения мак& роса, находящего продукты с ис& текшими запасами Рис. 3.2. Сдвиг диапазона ячеек с помо& щью команды Range("A1:C3").Offset (1, 1).Select Изменение размера диапазона ячеек с помощью свойства Resize Свойство Resize позволяет изменять размер диапазона ячеек, используя в качестве отправной точки текущую активную ячейку. Ниже приведен синтаксис использования свойства Resize: Range.Resize(КоличествоСтрок, КоличествоСтолбцов ) Чтобы создать диапазон ячеек B3:D13, используйте выражение Range("B3").Resize(RowSize:=11, ColumnSize:=3) или его сокращенную форму Range("B3").Resize(11, 3) Как и свойство Offset, свойство Resize не требует указания обоих параY метров одновременно. Чтобы увеличить размер диапазона ячеек до двух столбцов, используйте любое из следующих выражений: Range("B3").Resize(ColumnSize:=2) Range("B3").Resize(, 2) А вот как увеличить размер диапазона ячеек до двух строк: Range("B3").Resize(RowSize:=2) Range("B3").Resize(2) 101 102 Часть I Первые шаги Возвратимся к таблице с продуктами питания. Чтобы найти продукт, запаY сы которого заканчиваются, и отметить это путем выделения цветом ячеек с названием продукта и его запасами, воспользуйтесь следующим макросом (рис. 3.3): Set Rng = Range("B1:B16").Find(What:="0", LookAt:=xlWhole, _ LookIn:=xlValues) Rng.Offset(, -1).Resize(, 2).Interior.ColorIndex = 15 Рис. 3.3. Изменение размера диапазона ячеек в действии Здесь свойство Offset используется для изменения активной ячейки, а свойство Resize — для увеличения размера диапазона до двух столбцов. Точно так же можно изменить и размер диапазона, состоящего из нескольY ких ячеек. К примеру, чтобы увеличить размер именованного диапазона до двух столбцов, воспользуйтесь следующим выражением: Range("Продукты").Resize(, 2) Помните, что параметры свойства Resize обозначают размер целевого диапазона ячеек, который необходимо создать. Обращение к диапазону ячеек с помощью свойств Columns и Rows Свойства Columns и Rows используются для обращения к столбцам и строкам диапазона ячеек и возвращают соответствующий объект Range. Ранее мы рассматривали следующую строку кода макроса: FinalRow = Range("A65536").End(xlUp).Row В результате ее выполнения переменной FinalRow присваивается номер последней строки (объект Range), столбец A которой содержит какиеYлибо данные. Зная номер последней строки с данными, можно создать цикл, поY очередно обрабатывающий все значащие строки рабочего листа. Работа с диапазоном ячеек Глава 3 103 Внимание Для корректного использования некоторых свойств объектов Columns и Rows не& обходимо наличие непрерывного диапазона ячеек. К примеру, результат следую& щего выражения будет равен 9, так как подсчет строк будет проведен только по первому диапазону ячеек: Range("A1:B9, C10:D19").Rows.Count Если же не группировать несмежные диапазоны ячеек (как показано ниже), то ре& зультат подсчета количества строк будет равен 19: Range("A1:B9", "C10:D19").Rows.Count Объединение диапазонов ячеек с помощью метода Union Метод Union позволяет объединить два или более несоприкасающихся диапазона ячеек. Он возвращает временный объект, предназначенный для манипулирования объединенным диапазоном: Application.Union(аргумент 1 ,аргумент 2 ,...) В результате выполнения приведенного ниже кода два именованных диаY пазона ячеек будут объединены, заполнены случайными числовыми значеY ниями и выделены путем утолщения шрифта: Set UnionRange = Union(Range("Диапазон1"), Range("Диапазон2")) With UnionRange ' В англоязычной версии Excel: ' .Formula = "=RAND()" .FormulaLocal = "=СЛЧИС()" .Font.Bold = True End With Создание нового диапазона ячеек из пересекающихся диапазонов с помощью метода Intersect Метод Intersect возвращает диапазон ячеек, полученный в результате пересечения нескольких диапазонов: Application.Intersect(аргумент 1 ,аргумент 2 ,...) В результате выполнения приведенного ниже кода будет создан новый диапаY зон ячеек, полученный в результате пересечения двух существующих диапазонов. Ячейки нового диапазона выделены цветом, как показано на рис. 3.4. Set IntersectRange = Intersect(Range("Диапазон1"), _ Range("Диапазон2")) IntersectRange.Interior.ColorIndex = 6 104 Часть I Первые шаги Рис. 3.4. Метод Intersect возвращает диапазон ячеек, по& лученный в результате пересечения нескольких диапазонов Проверка пустых ячеек с помощью функции IsEmpty Функция IsEmpty возвращает булево значение, определяющее, является ячейка пустой (True) или нет (False). Ячейка является пустой, если она не содержит какихYлибо данных (даже символов пробела). IsEmpty(Ячейка) На рис. 3.5 показана таблица с несколькими группами данных, разделенY ными пустой строкой. Рис. 3.5. Группы данных разделены пустой строкой С помощью следующего кода проведем поиск пустых строк (точнее, пусY тых ячеек в столбце A) и выделим цветом их первые 4 ячейки (рис. 3.6): LastRow = Range("A65536").End(xlUp).Row For i = 1 To LastRow If IsEmpty(Cells(i, 1)) Then Cells(i, 1).Resize(1, 4).Interior.ColorIndex = 1 Работа с диапазоном ячеек Глава 3 105 End If Next i Рис. 3.6. Первые 4 ячейки строк&разделителей выделены черным цветом Обращение к диапазону ячеек с помощью свойства CurrentRegion Свойство CurrentRegion возвращает объект, представляющий непреY рывный диапазон ячеек. С помощью этого свойства можно обратиться к диаY пазону ячеек, ограниченному по крайней мере одной пустой строкой или одY ним пустым столбцом: ДиапазонЯчеек.CurrentRegion В результате выполнения приведенной ниже строки кода будет выделен диапазон ячеек A1:D3 — непрерывный диапазон ячеек, включающий в себя ячейку A1 (рис. 3.7): Range("A1").CurrentRegion.Select Рис. 3.7. Используйте свойство CurrentRegion для обращения к непрерывному диапа& зону ячеек, включающему в себя текущую ак& тивную ячейку 106 Часть I Первые шаги Свойство CurrentRegion рекомендуется использовать для обращения к таблицам, размер которых постоянно меняется. Практикум Выделение ячеек, соответствующих определенному критерию, с помощью метода SpecialCells Далеко не все пользователи Excel знают о существовании диалогового окна Выделение группы ячеек (Go To Special). Нажмите клавишу <F5>, чтобы открыть диалоговое окно Переход (Go To) (рис. 3.8). Рис. 3.8. Чтобы открыть диалоговое окно Выделение группы ячеек, щелкните на кноп& ке Выделить Щелкните на кнопке Выделить (Special) в левом нижнем углу диалогового окна Переход, чтобы открыть диалоговое окно Выделение группы ячеек (рис. 3.9). Диалоговое окно Выделение группы ячеек позволяет выделить только пустые ячейки, только видимые ячейки или же только ячейки, содержащие формулы. Возможность выделения только видимых ячеек очень полезна при автоматиче& ской фильтрации данных. Возможности диалогового окна Выделение группы ячеек могут быть реализованы с помощью метода VBA SpecialCells. Этот метод позволяет работать с ячейка& ми, соответствующими определенному критерию: ДиапазонЯчеек.SpecialCells(Тип, Значение) Метод SpecialCells имеет два параметра: Тип и Значение (необязательный параметр). Тип ячейки может быть описан одной из констант xlCellType: xlCellTypeAllFormatConditions xlCellTypeAllValidation xlCellTypeBlanks xlCellTypeComments Работа с диапазоном ячеек Глава 3 107 xlCellTypeConstants xlCellTypeFormulas xlCellTypeLastCell xlCellTypeSameFormatConditions xlCellTypeSameValidation xlCellTypeVisible Рис. 3.9. Диалоговое окно Выделение группы ячеек предлагает широкие возможности по выделению ячеек Предусмотрено также 4 различных значения ячейки: xlErrors xlLogical xlNumbers xlTextValues В результате выполнения приведенного ниже кода вокруг всех непрерывных диа& пазонов ячеек с условным форматированием будет создана граница. При отсутст& вии таких диапазонов будет выдано сообщение об ошибке: Set rngCond = ActiveSheet.Cells.SpecialCells( _ xlCellTypeAllFormatConditions) If Not rngCond Is Nothing Then rngCond.BorderAround xlContinuous End If В таблице, показанной на рис. 3.10, отсутствуют данные в некоторых ячейках. Несмотря на эстетическую привлекательность такого решения, оно сводит на нет возможность сортировки данных таблицы. Подобный формат принят и в сводных таблицах Excel. К счастью, метод SpecialCells позволяет выделить все пустые ячейки в диапа& зоне и заполнить их нужными данными: Sub FillIn() Range("A1").CurrentRegion.SpecialCells( _ xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C" 108 Часть I Первые шаги Range("A1").CurrentRegion.Value = _ Range("A1").CurrentRegion.Value End Sub Рис. 3.10. Отсутствие данных в некото& рых ячейках делает невозможной сор& тировку таблицы В приведенном выше коде выражение Range("A1").CurrentRegion соответст& вует непрерывному диапазону ячеек рабочего листа. Свойство SpecialCells возвращает только пустые ячейки этого диапазона. Формула в стиле R1C1 (см. гла& ву 6, “Стиль записи ссылок R1C1”) заполняет каждую пустую ячейку данными из ячейки, расположенной на одну строку выше. Вторая строка кода представляет собой быстрый способ выполнения команд Копирование (Copy) и Специальная вставка (Paste Special). Результат выполнения кода показан на рис. 3.11. Рис. 3.11. После выполнения макроса пустые ячейки в таблице заполнились нужными данными Обращение к диапазону несмежных ячеек с помощью коллекции Areas Коллекция Areas используется для представления множества диапазонов несмежных ячеек. Она состоит из объектов Range, соответствующих непрерывY ным диапазонам ячеек в выделенной области. Если последняя состоит из одного непрерывного диапазона ячеек, коллекция Areas содержит один объект Range. Работа с диапазоном ячеек Глава 3 109 Рассмотрим задачу копирования данных о запасах продуктов (ячейки, выY деленные серым цветом) в другую часть рабочего листа (рис. 3.12). Рис. 3.12. Содержимое ячеек, выделенных серым цветом, необходимо скопировать в другую часть рабочего листа Наиболее очевидное решение заключается в создании цикла, поочередно копирующего значения всех необходимых ячеек. Однако существует и более эффективный подход (рис. 3.13): Set NewDestination = ActiveSheet.Range("I1") For Each Rng In Cells.SpecialCells(xlCellTypeConstants, 1).Areas Rng.Copy Destination:=NewDestination Set NewDestination = NewDestination.Offset(Rng.Rows.Count) Next Rng Рис. 3.13. Коллекция Areas предоставляет возможность эффективного мани& пулирования диапазонами несмежных ячеек Следующий шаг В следующей главе рассматриваются функции, определенные пользоватеY лем, а также наиболее распространенные задачи программирования в Excel. Глава 4 Ôóíêöèè, îïðåäåëåííûå ïîëüçîâàòåëåì Создание функций, определенных пользователем Иногда огромного количества встроенных функций Excel бывает недостаточно. В частности, Excel не содержит готового решения для задаY чи суммирования значений в ячейках, выделенных определенным цветом. Что же делать? Вручную скопироY вать все нужные ячейки в другую часть рабочего листа? Или взять калькулятор и провести подсчет саY мому? Оба способа отнимают много времени и не гарантируют отсутствие ошибок. Одно из возможных решений заключается в написании процедуY ры YYYY в конечном итоге, именно проY цедурам посвящена большая часть этой книги. Однако единственно праY вильным решением является создание функции, определенной пользователем. VBA позволяет создавать функY ции, которые могут использоваться аналогично встроенным функциям Excel, таким как СУММ (SUM). Чтобы применить подобную функцию, неY обходимо знать только ее имя и арY гументы. 4 Создание функций, определенных пользователем .. 111 Наиболее распространенные задачи программирования в Excel ...............................................113 Следующий шаг............................140 112 Часть I Первые шаги На заметку Функции, определенные пользователем, должны храниться в стандартных моду& лях. Модули рабочих листов и модуль ЭтаКнига (ThisWorkbook) являются специ& альными модулями. Функция, размещенная в одном из таких модулей, не будет воспринята Excel как функция, определенная пользователем. Практикум Практикум: пример создания и применения функции, определенной пользователем Создадим функцию, суммирующую значения двух ячеек, и применим ее на рабо& чем листе Excel. С помощью редактора Visual Basic добавьте к проекту новый модуль и введите в него текст функции суммирования значений двух ячеек Add (см. ниже). Эта функ& ция принимает два аргумента: Add(Number1, Number2) Здесь Number1 — это первое слагаемое, а Number2 — второе: Function Add(Number1, Number2) As Integer Add = Number1 + Number2 End Function Попытаемся разобраться в приведенном выше коде: имя функции — Add; аргументы функции Add — Number1 и Number2 — перечислены в скобках по& сле ее имени; возвращаемый функцией Add результат является целым числом (As Integer) и вычисляется по формуле Add = Number1 + Number2. Чтобы применить функцию Add на рабочем листе, выполните следующие действия. 1. Введите любые два числа в ячейки A1 и A2. 2. Выделите ячейку A3. 3. Нажмите комбинацию клавиш <Shift+F3> или выберите команду меню Excel Вставка Функция (Insert Function), чтобы открыть диалоговое окно мастера функций. 4. В раскрывающемся списке Категория (Or select a category) выберите значение Определенные пользователем (User Defined). 5. Выберите функцию Add и щелкните на кнопке OK. 6. В качестве первого аргумента укажите ячейку A1. 7. В качестве второго аргумента укажите ячейку A2. 8. Щелкните на кнопке OK. Поздравляем! Вы только что создали собственную функцию и применили ее на рабочем листе. Функции, определенные пользователем Глава 4 Большинство функций, используемых на рабочих листах, могут с успехом применяться в VBA, и наоборот. Тем не менее, VBA требует, чтобы функция, определенная пользователем (Add), вызывалась из процедуры (Addition), как показано ниже: Sub Addition () Dim Total as Integer Total = Add (1, 10) 'вызов функции, определенной пользователем MsgBox "Ответ: " & Total End Sub Наиболее распространенные задачи программирования в Excel В следующих разделах этой главы рассматриваются решения наиболее распространенных задач, встречающихся при повседневном программиY ровании в Excel. Вывод имени файла текущей рабочей книги в ячейке Предназначение следующей функции заключается в выводе имени файла активной рабочей книги в ячейке, как показано на рис. 4.1: MyName() Рис. 4.1. Функции MyName и MyFullName используются для вывода в ячейке имени и полного имени файла активной рабочей книги, соответственно Функция MyName не имеет аргументов. Function MyName() As String MyName = ThisWorkbook.Name End Function Вывод полного имени файла текущей рабочей книги в ячейке Предназначение следующей функции заключается в выводе имени файла активной рабочей книги в ячейке (см. рис. 4.1): MyFullName() Функция MyFullName не имеет аргументов. Function MyFullName() As String MyFullName = ThisWorkbook.FullName End Function 113 114 Часть I Первые шаги Как проверить, открыта ли рабочая книга Иногда требуется проверить, открыта ли определенная рабочая книга. Следующая функция возвращает значение True, если рабочая книга открыта, и False YYYY в противном случае: BookOpen(Bk) Функция BookOpen имеет один аргумент: Bk — имя файла рабочей книги. Function BookOpen(Bk As String) As Boolean Dim T As Excel.Workbook 'Удалить информацию об ошибках. Err.Clear 'Если при выполнении кода возникнет ошибка, она будет пропущена. On Error Resume Next Set T = Application.Workbooks(Bk) BookOpen = Not (T Is Nothing) 'Если рабочая книга открыта, переменная T будет содержать 'объект рабочей книги и, таким образом, не будет пустой. Err.Clear On Error GoTo 0 End Function Ниже приведен пример использования функции BookOpen: Sub OpenAWorkbook() Dim IsOpen As Boolean Dim BookName As String BookName = "Chapter 4 samples.xls" 'Вызов функции BookOpen - не забудьте указать значение параметра. IsOpen = BookOpen(BookName) If IsOpen Then MsgBox BookName & " открыта!!" Else Workbooks.Open (BookName) End If End Sub Проверка существования рабочего листа в открытой книге Следующая функция возвращает значение True, если указанный рабочий лист существует, и False — в противном случае. Подобная проверка возможY на только при условии, что соответствующая рабочая книга открыта. SheetExists(SName, WBName) Функция SheetExists имеет 2 аргумента: SName — имя рабочего листа; WBName — имя рабочей книги (необязательный параметр). Function SheetExists(SName As String, Optional WBName As _ String) As Boolean Dim WS As Worksheet Dim WB As Workbook Функции, определенные пользователем Глава 4 On Error Resume Next 'Проверить, задано ли имя файла рабочей книги. If Len(WBName) > 0 Then Set WB = Workbooks(WBName) 'Завершить выполнение, если рабочая книга не открыта. If WB Is Nothing Then Exit Function Else Set WB = ActiveWorkbook End If Set WS = WB.Sheets(SName) 'Если рабочий лист существует, переменная WS хранит 'соответствующий объект. Если рабочий лист отсутствует, 'переменная WS хранит значение Nothing. 'Если переменная WS НЕ хранит Nothing, значение 'выражения Not (WS Is Nothing) будет равно True. SheetExists = Not (WS Is Nothing) End Function Ниже приведен пример использования функции SheetExists: Sub CheckForSheet() Dim ShtExists As Boolean ShtExists = SheetExists("Sheet9") 'Обратите внимание, что функции был передан только один параметр. If ShtExists Then MsgBox "Рабочий лист существует!" Else MsgBox "Рабочий лист НЕ существует!" End If End Sub Подсчет количества файлов рабочих книг в папке Следующая функция просматривает папку (и при необходимости ее подпапки) и, в зависимости от переданных параметров, подсчитывает лиY бо общее количество хранящихся в ней файлов рабочих книг Excel, либо количество файлов рабочих книг Excel, имена которых включают в себя заданную строку. NumFilesInCurDir(LikeText, Subfolders) Функция NumFilesInCurDir имеет 2 аргумента: LikeText — строка, которую должно включать в себя имя файла рабоY чей книги (необязательный параметр); Subfolders — булево значение, определяющее необходимость провеY дения поиска в подпапках; по умолчанию поиск в подпапках не провоY дится (False) (необязательный параметр). Function NumFilesInCurDir(Optional LikeText As String, _ Optional Subfolders As Boolean = False) With Application.FileSearch .NewSearch 'Строка, которую должно включать в себя имя файла рабочей книги. If Len(LikeText) > 0 Then .Filename = LikeText 115 116 Часть I Первые шаги End If 'Выбрать тип файла - рабочие книги Excel. .FileType = msoFileTypeExcelWorkbooks 'Указать на необходимость проведения поиска в текущей папке. .LookIn = CurDir 'Указать на необходимость проведения поиска в подпапках. .SearchSubFolders = Subfolders .Execute NumFilesInCurDir = .FoundFiles.Count End With End Function Ниже приведен пример использования функции NumFilesInCurDir: Sub CountMyWkbks() Dim MyFiles As Integer MyFiles = NumFilesInCurDir("Глава*", True) MsgBox MyFiles & " файл(ов) найден(о)" End Sub Получение имени пользователя, зарегистрировавшегося в системе Следующая функция возвращает имя пользователя, зарегистрировавшегоY ся в системе. Вместе с функцией, возвращающей постоянное значение даты и времени (рассматривается далее в этой главе), она может быть применена для создания файла журнала. Кроме того, с ее помощью можно узнать имеющиеся у пользователя права на доступ к рабочей книге. WinUsername() Функция WinUsername не имеет аргументов. На заметку Функция WinUsername использует функции интерфейса прикладного програм& мирования (API), который рассматривается в главе 22, “Интерфейс прикладного программирования (API) Windows”. Следующий фрагмент кода должен быть помещен в верхнюю часть модуля: Private Declare Function WNetGetUser Lib "mpr.dll" Alias _ "WNetGetUserA" (ByVal lpName As String, ByVal lpUserName _ As String, lpnLength As Long) As Long Private Const NO_ERROR = 0 Private Const ERROR_NOT_CONNECTED = 2250& Private Const ERROR_MORE_DATA = 234 Private Const ERROR_NO_NETWORK = 1222& Private Const ERROR_EXTENDED_ERROR = 1208& Private Const ERROR_NO_NET_OR_BAD_PATH = 1203& Текст функции WinUsername может быть помещен в любую часть модуля при условии, что он будет находиться ниже объявлений Private: Функции, определенные пользователем Глава 4 Function WinUsername() As String 'Переменные: Dim strBuf As String, lngUser As Long, strUn As String 'Подготовка строковой переменной для использования в функции API. strBuf = Space$(255) 'Использование функции WNetGetUser, возвращающей имя пользователя. 'Сохранение возвращенного функцией кода в переменной lngUser. lngUser = WNetGetUser("", strBuf, 255) 'Если выполнение функции API прошло успешно, If lngUser = NO_ERROR Then 'убрать пробелы из переменной strBuf и возвратить результат 'выполнения функции WinUsername. strUn = Left(strBuf, InStr(strBuf, vbNullChar) - 1) WinUsername = strUn Else 'Ошибка, завершение работы функции. WinUsername = "Ошибка :" & lngUser End If End Function Ниже приведен пример использования функции WinUsername: Sub CheckUserRights() Dim UserName As String UserName = WinUsername Select Case UserName Case "Administrator" MsgBox "Полные права" Case "Guest" MsgBox "Вы не можете вносить изменения в рабочую книгу" Case Else MsgBox "Ограниченные права" End Select End Sub Получение даты и времени последнего сохранения рабочей книги Следующая функция возвращает дату и время последнего сохранения раY бочей книги, как показано на рис. 4.2. LastSaved(FullPath) Рис. 4.2. Функция LastSaved возвращает дату и время последнего сохранения рабочей книги Функция LastSaved имеет один аргумент: FullPath — полный путь к файлу рабочей книги. 117 118 Часть I Первые шаги Function LastSaved(FullPath As String) As Date LastSaved = FileDateTime(FullPath) End Function Получение постоянного значения даты и времени Поскольку значение, возвращаемое функцией Now, обновляется при кажY дом открытии рабочей книги, его не рекомендуется использовать для указаY ния даты и времени создания или изменения рабочей книги. Несмотря на то что следующая функция основана на функции Now, ее результат куда менее динамичен, так как он обновляется только при обновлении соответствующей ячейки (рис. 4.3). DateTime() Рис. 4.3. Функция DateTime возвращает постоянное значение даты и времени Функция DateTime не имеет аргументов. На заметку Результат выполнения функции DateTime должен быть размещен в соответст& вующим образом отформатированной ячейке. Function DateTime() DateTime = Now End Function Проверка адреса электронной почты Следующая функция проверяет корректность написания адреса электронY ной почты (рис. 4.4). IsEmailValid(StrEmail) Рис. 4.4. Проверка корректности написания адреса электронной почты Функции, определенные пользователем Глава 4 Внимание Функция IsEmailValid проверяет только корректность написания адреса элек& тронной почты, а не факт его существования. Функция IsEmailValid имеет один аргумент: StrEmail — адрес электронной почты. Function IsEmailValid(strEmail As String) As Boolean Dim strArray As Variant Dim strItem As Variant Dim i As Long Dim c As String Dim blnIsItValid As Boolean blnIsItValid = True 'Подсчет количества знаков @ в строке. i = Len(strEmail) - Len(Application.Substitute(strEmail, _ "@", "")) 'Если знаков @ больше, чем 1, адрес электронной почты неверный. If i <> 1 Then IsEmailValid = False: Exit Function ReDim strArray(1 To 2) 'Текст слева и справа от знака @ помещается в 2 разные переменные. strArray(1) = Left(strEmail, InStr(1, strEmail, "@", 1) - 1) strArray(2) = Application.Substitute(Right(strEmail, _ Len(strEmail) - Len(strArray(1))), "@", "") For Each strItem In strArray 'Если хотя бы одна из переменных оказалась пустой, 'адрес электронной почты неверный. If Len(strItem) <= 0 Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If 'Проверка использования только допустимых символов. For i = 1 To Len(strItem) 'Чтобы упростить проверку, все символы переводятся в нижний регистр. c = LCase(Mid(strItem, i, 1)) If InStr("abcdefghijklmnopqrstuvwxyz_-.", c) <= 0 _ And Not IsNumeric(c) Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If Next i 'Проверка, что первым символом строк слева и справа от @ 'не является символ точки (.). If Left(strItem, 1) = "." Or Right(strItem, 1) = "." Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If Next strItem 119 120 Часть I Первые шаги 'Проверка, что в строке справа от @ есть символ точки (.). If InStr(strArray(2), ".") <= 0 Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If i = Len(strArray(2)) - InStrRev(strArray(2), ".") 'Проверка длины имени домена. If i <> 2 And i <> 3 And i <> 4 Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If 'Проверка отсутствия двух символов точки подряд (..). If InStr(strEmail, "..") > 0 Then blnIsItValid = False IsEmailValid = blnIsItValid Exit Function End If IsEmailValid = blnIsItValid End Function Суммирование значений ячеек на основе цвета заливки Следующая функция суммирует значения ячеек на основе цвета заливки. SumByColor(CellColor, SumRange) На заметку Функция SumByColor не поддерживает ячейки с условным форматированием. Таким образом, наличие заливки является ключевым условием, необходимым для выполнения функции. Функция SumByColor имеет 2 аргумента: CellColor — адрес ячейки, имеющей заливку нужного цвета; SumRange — диапазон ячеек, в котором необходимо провести поиск ячеек с определенным цветом заливки. Function SumByColor(CellColor As Range, SumRange As Range) Dim myCell As Range Dim iCol As Integer Dim myTotal 'Определение цвета заливки ячейки. iCol = CellColor.Interior.ColorIndex 'Просмотр ячеек в указанном диапазоне. For Each myCell In SumRange 'Если цвет заливки ячейки совпадает с указанным цветом, If myCell.Interior.ColorIndex = iCol Then 'добавить ее значение к сумме. myTotal = WorksheetFunction.Sum(myCell) + myTotal End If Next myCell Функции, определенные пользователем Глава 4 SumByColor = myTotal End Function Пример использования функции SumByColor на рабочем листе можно увидеть на рис. 4.5. Рис. 4.5. Пример суммирования значений ячеек на основе цвета заливки Получение имени и номера цвета заливки ячейки Следующая функция возвращает имя и номер цвета заливки ячейки: CellColor(myCell, ColorIndex) На заметку Функция CellColor не поддерживает ячейки с условным форматированием. На& личие заливки является ключевым условием, необходимым для выполнения функции. Функция CellColor имеет 2 аргумента: myCell — адрес ячейки; ColorIndex — если данный необязательный параметр имеет значение True, функция CellColor возвратит номер цвета заливки ячейки (по умолчанию функция CellColor возвращает имя цвета заливки). Function CellColor(myCell As Range, Optional ColorIndex As Boolean) Dim myColor As String, IndexNum As Integer Select Case myCell.Interior.ColorIndex Case 1 myColor = "Черный" IndexNum = 1 Case 2 myColor = "Белый" 121 122 Часть I Первые шаги IndexNum = 2 Case 3 myColor = "Красный" IndexNum = 3 Case 4 myColor = "Ярко-зеленый" IndexNum = 4 Case 5 myColor = "Синий" IndexNum = 5 Case 6 myColor = "Желтый" IndexNum = 6 Case 7 myColor = "Лиловый" IndexNum = 7 Case 8 myColor = "Бирюзовый" IndexNum = 8 Case 9 myColor = "Темно-красный" IndexNum = 9 Case 10 myColor = "Зеленый" IndexNum = 10 Case 11 myColor = "Темно-синий" IndexNum = 11 Case 12 myColor = "Коричнево-зеленый" IndexNum = 12 Case 13 myColor = "Фиолетовый" IndexNum = 13 Case 14 myColor = "Сине-зеленый" IndexNum = 14 Case 15 myColor = "Серый 25%" IndexNum = 15 Case 16 myColor = "Серый 50%" IndexNum = 16 Case 33 myColor = "Голубой" IndexNum = 33 Case 34 myColor = "Светло-бирюзовый" IndexNum = 34 Case 35 myColor = "Бледно-зеленый" IndexNum = 35 Case 36 myColor = "Светло-желтый" IndexNum = 36 Case 37 myColor = "Бледно-голубой" Функции, определенные пользователем IndexNum = 37 Case 38 myColor = "Розовый" IndexNum = 38 Case 39 myColor = "Сиреневый" IndexNum = 39 Case 40 myColor = "Светло-коричневый" IndexNum = 40 Case 41 myColor = "Темно-голубой" IndexNum = 41 Case 42 myColor = "Темно-бирюзовый" IndexNum = 42 Case 43 myColor = "Травяной" IndexNum = 43 Case 44 myColor = "Золотистый" IndexNum = 44 Case 45 myColor = "Светло-оранжевый" IndexNum = 45 Case 46 myColor = "Оранжевый" IndexNum = 46 Case 47 myColor = "Сизый" IndexNum = 47 Case 48 myColor = "Серый 40%" IndexNum = 48 Case 49 myColor = "Светло-сизый" IndexNum = 49 Case 50 myColor = "Изумрудный" IndexNum = 50 Case 51 myColor = "Темно-зеленый" IndexNum = 51 Case 52 myColor = "Оливковый" IndexNum = 52 Case 53 myColor = "Коричневый" IndexNum = 53 Case 54 myColor = "Вишневый" IndexNum = 54 Case 55 myColor = "Индиго" IndexNum = 55 Case 56 myColor = "Серый 80%" Глава 4 123 124 Часть I Первые шаги IndexNum = 56 Case Else myColor = "Другой цвет или отсутствие заливки" End Select 'Возвратить номер цвета заливки ячейки, если это указано 'при вызове функции или невозможно возвратить имя цвета заливки. If ColorIndex = True Or myColor = "Другой цвет или _ отсутствие заливки" Then CellColor = IndexNum Else CellColor = myColor End If End Function Пример использования функции CellColor на рабочем листе показан на рис. 4.6. Рис. 4.6. Пример определения имени или номера цвета заливки ячейки с помощью функции CellColor Получение номера цвета текста в ячейке Следующая функция возвращает номер цвета текста в ячейке: TextColor(Rng) На заметку Функция TextColor не поддерживает ячейки с условным или автоматическим форматированием текста (примером последнего является выделение отрицатель& ных числовых значений красным цветом). Домашнее задание Изучив функцию TextColor, измените рассматривавшуюся в предыдущем раз& деле функцию CellColor таким образом, чтобы она возвращала имя цвета текста в ячейке. Функция TextColor имеет один аргумент: Rng — адрес ячейки. Function TextColor(Rng As Range) As Long TextColor = Rng.Range("A1").Font.ColorIndex End Function Функции, определенные пользователем Глава 4 125 Подсчет количества уникальных значений Следующая функция возвращает количество уникальных значений в укаY занном диапазоне ячеек (рис. 4.7): NumUniqueValues(Rng) Рис. 4.7. Пример подсчета количества уникальных значений в диапазоне ячеек с помощью функции NumUniqueValues Функция NumUniqueValues имеет один аргумент: Rng — адрес диапазона ячеек. Function NumUniqueValues(Rng As Range) As Long Dim myCell As Range, UniqueVals As New Collection 'Произвести пересчет результата при изменении диапазона ячеек. Application.Volatile 'Поместить значения всех ячеек диапазона в коллекцию '(в коллекции могут находиться только уникальные значения). 'Продолжить выполнение функции при возникновении ошибки '(помещение в коллекцию двух одинаковых элементов). On Error Resume Next For Each myCell In Rng UniqueVals.Add myCell.Value, CStr(myCell.Value) Next myCell 'Вернуться к стандартному режиму обработки ошибок. On Error GoTo 0 'Возвратить количество элементов в коллекции. NumUniqueValues = UniqueVals.Count End Function Удаление повторяющихся значений из диапазона ячеек Следующая функция удаляет повторяющиеся значения из указанного диаY пазона ячеек: UniqueValues(OrigArray) 126 Часть I Первые шаги Функция UniqueValues имеет один аргумент: OrigArray — массив, из которого необходимо удалить повторяющиеY ся значения. Следующий фрагмент кода должен быть помещен в верхнюю часть модуля: Const Const Const Const ERR_BAD_PARAMETER = "Не указан исходный массив" ERR_BAD_TYPE = "Неверный тип" ERR_BP_NUMBER = 20000 ERR_BT_NUMBER = 20001 Текст функции UniqueValues может быть помещен в любую часть модуY ля при условии, что он будет находиться ниже объявлений Const: Public Function UniqueValues(ByVal OrigArray As Variant) As Variant Dim vAns() As Variant Dim lStartPoint As Long Dim lEndPoint As Long Dim lCtr As Long, lCount As Long Dim iCtr As Integer Dim col As New Collection Dim sIndex As String Dim vTest As Variant, vItem As Variant Dim iBadVarTypes(4) As Integer 'Завершить выполнение функции, если массив содержит 'элемент одного из следующих типов. iBadVarTypes(0) = vbObject iBadVarTypes(1) = vbError iBadVarTypes(2) = vbDataObject iBadVarTypes(3) = vbUserDefinedType iBadVarTypes(4) = vbArray 'Проверить, является ли переданное функции значение массивом. If Not IsArray(OrigArray) Then Err.Raise ERR_BP_NUMBER, , ERR_BAD_PARAMETER Exit Function End If lStartPoint = LBound(OrigArray) lEndPoint = UBound(OrigArray) For lCtr = lStartPoint To lEndPoint vItem = OrigArray(lCtr) 'Проверить допустимость значений элементов массива. For iCtr = 0 To UBound(iBadVarTypes) If VarType(vItem) = iBadVarTypes(iCtr) Or _ VarType(vItem) = iBadVarTypes(iCtr) + vbVariant Then Err.Raise ERR_BT_NUMBER, , ERR_BAD_TYPE Exit Function End If Next iCtr 'Добавить элемент в коллекцию, используя его значение 'в качестве индекса. При добавлении в коллекцию 'уже существующего элемента возникнет ошибка. sIndex = CStr(vItem) Функции, определенные пользователем Глава 4 127 'Автоматически добавить первый элемент в коллекцию. If lCtr = lStartPoint Then col.Add vItem, sIndex ReDim vAns(lStartPoint To lStartPoint) As Variant vAns(lStartPoint) = vItem Else On Error Resume Next col.Add vItem, sIndex If Err.Number = 0 Then lCount = UBound(vAns) + 1 ReDim Preserve vAns(lStartPoint To lCount) vAns(lCount) = vItem End If End If Err.Clear Next lCtr UniqueValues = vAns End Function Ниже приведен пример использования функции UniqueValues: Function NoDupsArray(Rng As Range) As Variant Dim arr1() As Variant If Rng.Columns.Count > 1 Then Exit Function arr1 = Application.Transpose(Rng) arr1 = UniqueValues(arr1) NoDupsArray = Application.Transpose(arr1) End Function Результат применения функции NoDupsArray на рабочем листе показан на рис. 4.8. Рис. 4.8. Создание диапазона ячеек, содержащего только уникальные зна& чения из исходного диапазона 128 Часть I Первые шаги Поиск первой непустой ячейки в диапазоне Следующая функция возвращает значение первой непустой ячейке в укаY занном диапазоне: FirstNonZeroLength(Rng) Функция FirstNonZeroLength имеет один аргумент: Rng — адрес диапазона ячеек. Function FirstNonZeroLength(Rng As Range) Dim myCell As Range FirstNonZeroLength = 0# For Each myCell In Rng If Not IsNull(myCell) And myCell <> "" Then FirstNonZeroLength = myCell.Value Exit Function End If Next myCell FirstNonZeroLength = myCell.Value End Function На рис. 4.9 показан пример использования функции FirstNonZeroLength на рабочем листе. Рис. 4.9. Пример нахождения значения первой непустой ячейки в диапазоне с помощью функции FirstNonZeroLength Замена нескольких символов в строке Следующая функция используется для замены нескольких символов в строке (рис. 4.10): MSubstitute(trStr, frStr, toStr) Рис. 4.10. Пример замены нескольких символов в строке с помощью функции MSubstitute Функции, определенные пользователем Глава 4 129 Функция MSubstitute имеет 3 аргумента: trStr — исходная строка; frStr — символы строки, подлежащие замене; toStr — символыYзаменители. Внимание Функция MSubstitute предполагает, что длина строки toStr совпадает с дли& ной строки frStr. Если длина строки toStr меньше длины строки frStr, не& достающие символы считаются пустыми ("").Функция MSubstitute учитывает также регистр символов. Так, чтобы заменить все вхождения в строку буквы “А”, в строке frStr следует указать символы а и A. Замена одного символа двумя не поддерживается. Результатом выражения =MSubstitute("Тестовая строка"; "о"; "$@") будет Тест$вая стр$ка Ниже приведен текст функции MSubstitute: Function MSubstitute(ByVal trStr As Variant, frStr As String, _ toStr As String) As Variant Dim iRow As Integer Dim iCol As Integer Dim j As Integer Dim Ar As Variant Dim vfr() As String Dim vto() As String ReDim vfr(1 To Len(frStr)) ReDim vto(1 To Len(frStr)) 'Помещение строк в массивы. For j = 1 To Len(frStr) vfr(j) = Mid(frStr, j, 1) If Mid(toStr, j, 1) <> "" Then vto(j) = Mid(toStr, j, 1) Else vto(j) = "" End If Next j 'Сравнивание каждого символа и, при необходимости, его замена. If IsArray(trStr) Then Ar = trStr For iRow = LBound(Ar, 1) To UBound(Ar, 1) For iCol = LBound(Ar, 2) To UBound(Ar, 2) For j = 1 To Len(frStr) Ar(iRow, iCol) = Application.Substitute( _ Ar(iRow, iCol), vfr(j), vto(j)) Next j Next iCol Next iRow Else Ar = trStr For j = 1 To Len(frStr) 130 Часть I Первые шаги Ar = Application.Substitute(Ar, vfr(j), vto(j)) Next j End If MSubstitute = Ar End Function Извлечение чисел из смешанного текста Следующая функция извлекает числа из смешанного текста (текста, соY держащего числа и буквы): RetrieveNumbers(myString) Пример использования функции RetrieveNumbers на рабочем листе поY казан на рис. 4.11. Рис. 4.11. Пример извлечения чисел из смешанного текста с помощью функ& ции RetrieveNumbers Функция RetrieveNumbers имеет один аргумент: myString — строка смешанного текста. Function RetrieveNumbers(myString As String) Dim i As Integer, j As Integer Dim OnlyNums As String 'Просмотр строки, начиная с ее конца (с шагом -1). For i = Len(myString) To 1 Step -1 'IsNumeric - это функция VBA, возвращающая True, 'если значение переменной является числом. 'Все найденные таким образом числа помещаются в строку OnlyNums. If IsNumeric(Mid(myString, i, 1)) Then j = j + 1 OnlyNums = Mid(myString, i, 1) & OnlyNums End If If j = 1 Then OnlyNums = CInt(Mid(OnlyNums, 1, 1)) Next i RetrieveNumbers = CLng(OnlyNums) End Function Преобразование номера недели в дату Следующая функция преобразовывает строку вида ‘‘Неделя НН ГГГГ’’ (где НН YYYY это номер недели, а ГГГГ YYYY номер года) в дату, соответствующую поY недельнику этой недели: Weekday(Str) Функции, определенные пользователем Глава 4 На заметку Результат выполнения функции Weekday должен быть помещен в ячейку, отфор& матированную для отображения даты. Пример использования функции Weekday на рабочем листе показан на рис. 4.12. Рис. 4.12. Пример преобразования номера недели в дату с помощью функции Weekday Функция Weekday имеет один аргумент: Str — строка вида ‘‘Неделя НН ГГГГ’’. Function ConvertWeekDay(str As String) As Date Dim Week As Long Dim FirstMon As Date Dim TStr As String FirstMon = DateSerial(Right(str, 4), 1, 1) FirstMon = FirstMon - FirstMon Mod 7 + 2 TStr = Right(str, Len(str) - 7) Week = Left(TStr, InStr(1, TStr, " ", 1)) + 0 ConvertWeekDay = FirstMon + (Week - 1) * 7 End Function Разбор строки с символамиLразделителями Следующая функция извлекает элемент с заданным номером из строки с символамиYразделителями: StringElement(str, chr, ind) Пример использования функции StringElement на рабочем листе покаY зан на рис. 4.13. Рис. 4.13. Пример извлечения элемента с заданным номером из стро& ки с символами&разделителями с помощью функции StringElement 131 132 Часть I Первые шаги Функция StringElement имеет 3 аргумента: str — строка с символамиYразделителями; chr — символYразделитель; ind — номер элемента, который нужно извлечь из строки. Function StringElement(str As String, chr As String, ind As Integer) Dim arr_str As Variant arr_str = Split(str, chr) StringElement = arr_str(ind - 1) End Function Сортировка и конкатенация значений ячеек из заданного диапазона Следующая функция сортирует значения ячеек из заданного диапазона и проводит их конкатенацию с помощью символаYразделителя ,: SortConcat(Rng) Пример использования функции SortConcat на рабочем листе показан на рис. 4.14. Рис. 4.14. Пример сортировки и конкатенации значений ячеек из заданного диапазона с помощью функции SortConcat Функция SortConcat имеет один аргумент: Rng — адрес диапазона ячеек. На заметку Для работы функции SortConcat используется процедура сортировки массива BubbleSort. Функции, определенные пользователем Глава 4 133 Function SortConcat(Rng As Range) As Variant Dim MySum As String, arr1() As String Dim j As Integer, i As Integer Dim cl As Range Dim concat As Variant On Error GoTo FuncFail: 'Инициализация результата функции. SortConcat = 0# 'Завершить выполнение функции, если диапазон ячеек пуст. If Rng.Count = 0 Then Exit Function 'Создать массив с размером, равным размеру диапазона ячеек. ReDim arr1(1 To Rng.Count) 'Заполнить массив. i = 1 For Each cl In Rng arr1(i) = cl.Value i = i + 1 Next 'Отсортировать элементы массива. Call BubbleSort(arr1) 'Создать строку из элементов массива. For j = UBound(arr1) To 1 Step -1 If Not IsEmpty(arr1(j)) Then MySum = arr1(j) & "," & MySum End If Next j 'Присвоить значение функции. SortConcat = Left(MySum, Len(MySum) - 2) 'Точка выхода из функции SortConcat. concat_exit: Exit Function 'Вывести в ячейке номер ошибки и ее описание. FuncFail: SortConcat = Err.Number & "-" & Err.Description Resume concat_exit End Function Следующая процедура реализует один из наиболее популярных методов сорY тировки массива, получившего название ‘‘метода пузырьковой сортировки’’: Sub BubbleSort(List() As String) 'Данная процедура сортирует содержимое массива по возрастанию. Dim First As Integer, Last As Integer Dim i As Integer, j As Integer Dim Temp First = LBound(List) Last = UBound(List) For i = First To Last - 1 For j = i + 1 To Last If UCase(List(i)) > UCase(List(j)) Then Temp = List(j) List(j) = List(i) List(i) = Temp End If Next j 134 Часть I Первые шаги Next i End Sub Сортировка числовых и строковых значений Следующая функция сортирует значения ячеек из смешанного диапазона (диапазона, содержащего как числовые, так и строковые значения) YYYY сперва в числовом, а затем в алфавитном порядке. Результат помещается в массив, коY торый может быть отображен на рабочем листе с помощью формулы массива. Sorter(Rng) Пример использования функции Sorter показан на рис. 4.15. Рис. 4.15. Сортировка значений ячеек из смешанного диапазона с помощью функции Sorter Функция Sorter имеет один аргумент: Rng — адрес диапазона ячеек. Function Sorter(Rng As Range) As Variant Dim arr1() As Variant If Rng.Columns.Count > 1 Then Exit Function arr1 = Application.Transpose(Rng) QuickSort arr1 'Возвратить массив. Sorter = Application.Transpose(arr1) End Function Для сортировки значений ячеек в смешанном диапазоне функция Sorter использует две процедуры. Public Sub QuickSort(ByRef vntArr As Variant, _ Optional ByVal lngLeft As Long = -2, _ Optional ByVal lngRight As Long = -2) Dim i, j, lngMid As Long Dim vntTestVal As Variant If lngLeft = -2 Then lngLeft = LBound(vntArr) If lngRight = -2 Then lngRight = UBound(vntArr) Функции, определенные пользователем Глава 4 135 If lngLeft < lngRight Then lngMid = (lngLeft + lngRight) \ 2 vntTestVal = vntArr(lngMid) i = lngLeft j = lngRight Do Do While vntArr(i) < vntTestVal i = i + 1 Loop Do While vntArr(j) > vntTestVal j = j - 1 Loop If i <= j Then Call SwapElements(vntArr, i, j) i = i + 1 j = j - 1 End If Loop Until i > j If j <= lngMid Then Call QuickSort(vntArr, Call QuickSort(vntArr, Else Call QuickSort(vntArr, Call QuickSort(vntArr, End If End If End Sub lngLeft, j) i, lngRight) i, lngRight) lngLeft, j) Private Sub SwapElements(ByRef vntItems As Variant, _ ByVal lngItem1 As Long, _ ByVal lngItem2 As Long) Dim vntTemp As Variant vntTemp = vntItems(lngItem2) vntItems(lngItem2) = vntItems(lngItem1) vntItems(lngItem1) = vntTemp End Sub Поиск строки в диапазоне ячеек Следующая функция проводит поиск строки в указанном диапазоне ячеек. Результатом выполнения функции являются адреса ячеек, в которых была найдена заданная строка. ContainsText(Rng, Text) Пример использования функции ContainsText показан на рис. 4.16. Рис. 4.16. Пример поиска строки в диапазоне ячеек с помощью функции ContainsText 136 Часть I Первые шаги Функция ContainsText имеет 2 аргумента: Rng — адрес диапазона ячеек; Text — строка, которую необходимо найти. Function ContainsText(Rng As Range, Text As String) As String Dim T As String Dim myCell As Range 'Просмотр каждой ячейки в диапазоне. For Each myCell In Rng 'Поиск указанной строки. If InStr(myCell.Text, Text) > 0 Then 'Если строка найдена, добавить ее адрес к результату 'выполнения функции. If Len(T) = 0 Then T = myCell.Address(False, False) Else T = T & "," & myCell.Address(False, False) End If End If Next myCell ContainsText = T End Function Запись содержимого ячейки в обратном порядке Следующая функция записывает содержимое ячейки в обратном порядке: ReverseContents(myCell, IsText) Функция ReverseContents имеет 2 аргумента: myCell — адрес ячейки; IsText — необязательный булев параметр, определяющий, является значение ячейки текстом (True, используется по умолчанию) или чисY лом (False). Function ReverseContents(myCell As Range, Optional IsText _ As Boolean = True) Dim i As Integer Dim OrigString As String, NewString As String 'Удалить символы пробела в начале и конце строки. OrigString = Trim(myCell) For i = 1 To Len(OrigString) 'Запись исходной строки в обратном порядке путем добавления 'строки NewString после символа исходной строки. NewString = Mid(OrigString, i, 1) & NewString Next i If IsText = False Then ReverseContents = CLng(NewString) Else ReverseContents = NewString End If End Function Функции, определенные пользователем Глава 4 137 Поиск наибольших значений в диапазоне ячеек Следующая функция возвращает адреса ячеек диапазона, содержащих наиY большее значение: ReturnMaxs(Rng) Пример использования функции ReturnMaxs на рабочем листе показан на рис. 4.17. Рис. 4.17. Пример нахождения адресов ячеек диапазона, содержа& щих наибольшее значение, с помощью функции ReturnMaxs Функция ReturnMaxs имеет один аргумент: Rng — адрес диапазона ячеек. Function ReturnMaxs(Rng As Range) As String Dim Mx As Double Dim myCell As Range 'Если диапазон состоит из одной ячейки, вернуть ее адрес 'в качестве результата выполнения функции. If Rng.Count = 1 Then ReturnMaxs = Rng.Address(False, _ False): Exit Function 'Использование встроенной функции Max для нахождения 'наибольшего значения в диапазоне ячеек. Mx = Application.Max(Rng) 'Зная максимальное значение в диапазоне ячеек, найти все 'ячейки, содержащие это значение, и возвратить их адреса. For Each myCell In Rng If myCell = Mx Then If Len(ReturnMaxs) = 0 Then ReturnMaxs = myCell.Address(False, False) Else ReturnMaxs = ReturnMaxs & "," & _ myCell.Address(False, False) 138 Часть I Первые шаги End If End If Next myCell End Function Получение адреса гиперссылки Следующая функция возвращает адрес гиперссылки: GetAddress(Hyperlink) Пример использования функции GetAddress на рабочем листе показан на рис. 4.18. Рис. 4.18. Пример получения адреса гиперссылки с помощью функции GetAddress Функция GetAddress имеет один аргумент: Hyperlink — адрес ячейки, содержащей гиперссылку. Function GetAddress(HyperlinkCell As Range) GetAddress = Replace(HyperlinkCell.Hyperlinks(1).Address, _ "mailto:", "") End Function Получение адреса столбца ячейки Следующая функция возвращает адрес столбца ячейки: ColName(Rng) Функция ColName имеет один аргумент: Rng — адрес ячейки. Function ColName(Rng As Range) As String ColName = Left(Rng.Range("A1").Address(True, False), _ InStr(1, Rng.Range("A1").Address(True, False), "$", 1) - 1) End Function Генерация постоянных случайных чисел Следующая функция используется для помещения в ячейку случайного числа. StaticRAND() В отличие от встроенной функции СЛЧИС (RAND), значение которой измеY няется при каждом открытии рабочей книги, значение функции StaticRAND Функции, определенные пользователем Глава 4 139 изменяется только при принудительном пересчете значения ячейки. Пример использования функции StaticRAND на рабочем листе показан на рис. 4.19. Функция StaticRAND не имеет аргументов. Function StaticRAND() As Double Randomize StaticRAND = Rnd End Function Рис. 4.19. Пример генерации постоянного случайного числа с по& мощью функции StaticRAND Использование структуры Select...Case Следующая функция демонстрирует пример использования структуры Select...Case для замены вложенных выражений If...Then...Else (рис. 4.20). Рис. 4.20. Пример замены вложенных выражений If...Then ...Else с помощью структуры Select...Case Function state_period(mth As Integer, yr As Integer) Select Case mth Case 1 state_period = "C 1 июля " & yr - 1 & " по _ 31 июля " & yr - 1 Case 2 state_period = "С 1 августа " & yr - 1 & " по _ 31 августа " & yr - 1 Case 3 state_period = "С 1 сентября " & yr - 1 & " по _ 30 сентября " & yr - 1 Case 4 140 Часть I Первые шаги state_period 31 октября " & yr - 1 Case 5 state_period 30 ноября " & yr - 1 Case 6 state_period 31 декабря " & yr - 1 Case 7 state_period 31 января " & yr Case 8 state_period 28 февраля " & yr Case 9 state_period 31 марта " & yr Case 10 state_period 30 апреля " & yr Case 11 state_period 31 мая " & yr Case 12 state_period 30 июня " & yr Case 13 state_period Case 14 state_period End Select End Function = "С 1 октября " & yr - 1 & " по _ = "С 1 ноября " & yr - 1 & " по _ = "С 1 декабря " & yr - 1 & " по _ = "С 1 января " & yr & " по _ = "С 1 февраля " & yr & " по _ = "С 1 марта " & yr & " по _ = "С 1 апреля " & yr & " по _ = "С 1 мая " & yr & " по _ = "С 1 июня " & yr & " по _ = "Подготовка к распродаже" = "Распродажа" Следующий шаг В следующей главе будет рассмотрен один из фундаментальных компонентов любого языка программирования YYYY цикл. Помимо базовых циклов, присутстY вующих практически в каждом языке программирования, будет рассмотрен цикл For Each...Next, являющийся исключительной особенностью VBA. Глава 5 Öèêëû è óïðàâëåíèå âûïîëíåíèåì êîäà Цикл YYYY это фундаментальный компонент любого языка програмY мирования. VBA поддерживает все наиболее распространенные виды циклов, а также специальный цикл, явY ляющийся исключительной особенноY стью VBA как представителя класY са объектноYориентированных языков программирования. В этой главе рассматриваются слеY дующие базовые конструкции циклов: For...Next; Do...While; Do...Until; While...Loop; Until...Loop. Также будет рассмотрен специальY ный цикл, уникальный для объектY ноYориентированных языков прогY раммирования: For Each...Next. Цикл For...Next For...Next — один из самых распространенных видов цикла, приY сутствующий практически в каждом языке программирования. Суть данY ного цикла заключается во множестY венном выполнении фрагмента кода, заключенного между выражениями For и Next, с различным значением переменнойYсчетчика (указывается в выражении For). 5 Цикл For...Next............................... 141 Циклы Do...Loop ............................ 147 Цикл For Each...Next...................... 152 Управление выполнением кода: использование конструкций If...Then...Else и Select Case ...................................... 155 Следующий шаг............................ 160 142 Часть I Первые шаги Рассмотрим следующий фрагмент кода: For I = 1 To 10 Cells(I, I).Value = i Next I ПеременнаяYсчетчик носит имя I. При первом выполнении цикла значеY ние переменной I равно 1. Это приводит к тому, что ячейке, расположенной в 1Yй строке 1Yго столбца, будет присвоено значение 1 (рис. 5.1). Рассмотрим действия VBA при достижении строки Next I. Перед выполY нением этой строки значение переменной I равно 1. После выполнения строY ки Next I VBA необходимо принять решение. Если после добавления к пеY ременнойYсчетчику 1 ее значение не превысило максимально допустимое знаY чение, заданное с помощью оператора To, цикл следует продолжить. В данном случае значение переменной I увеличится до 2 и выполнение кода будет проY должено с первой строки после выражения For. Значение переменной I до и после выполнения строки Next показано на рис. 5.2 и 5.3, соответственно. Рис. 5.1. При первом выполнении цикла ячейке, расположенной в 1&й строке 1&го столбца, будет присвое& но значение 1 Рис. 5.2. Перед выполнением строки Next I значение переменной I равно 1. Увеличе& ние переменной I на 1 не приведет к пре& вышению максимального значения, задан& ного с помощью оператора To Во время второго выполнения цикла значение переменной I равно 2, в реY зультате чего ячейке, расположенной во 2Yй строке 2Yго столбца, будет приY своено значение 2 (рис. 5.4). Рис. 5.3. После выполнения строки Next I значение переменной I равно 2. Выполне& ние кода будет продолжено с первой строки после выражения For Рис. 5.4. Во время второго выпол& нения цикла ячейке, расположен& ной во 2&й строке 2&го столбца, бу& дет присвоено значение 2 При последующих выполнениях цикла значение переменной I будет увеY личено до 3, 4 и т.д. На 10Yм шаге ячейке, расположенной в 10Yй строке 10Yго столбца, будет присвоено значение 10. Циклы и управление выполнением кода Глава 5 143 Рассмотрим, что произойдет с переменной I после выполнения строки Next I в 10Yй раз. Как показано на рис. 5.5, перед выполнением строки Next I в 10Yй раз значение переменной I равно 10. Как всегда, после увеличения значения переменнойYсчетчика VBA предY стоит принять решение относительно дальнейшего выполнения цикла. ВыY полнение строки Next I в 10Yй раз приводит к увеличению значения переY менной I до 10, что больше, чем максимальное значение, заданное с помощью оператора To. VBA завершает цикл и переходит к выполнению первой строки кода после выражения Next (рис. 5.6). Рис. 5.5. Перед выполнением строки Next I в 10&й раз значение переменной I равно 10 Рис. 5.6. После увеличения значения пе& ременной I до 11 VBA выходит из цикла и продолжает выполнение кода с пер& вой строки после выражения Next При намерении использовать переменную I после выполнения цикла слеY дует помнить, что ее значение может превысить максимально допустимое знаY чение, заданное с помощью оператора To. Результат выполнения цикла после 10 итераций показан на рис. 5.7. Рис. 5.7. Результат выполнения цикла после 10 итераций Наиболее распространенное применение цикла For...Next заключается в обработке строк заданного диапазона на основе некоторого критерия. ПриY веденный ниже цикл используется для выделения всех строк, содержащих поY ложительное число в столбце F: For i = 2 To 10 If Cells(i, 6).Value > 0 Then Cells(i, 8).Value = "Выручка от сервиса" Cells(i, 1).Resize(1, 8).Interior.ColorIndex = 4 End If Next i 144 Часть I Первые шаги Данный цикл обрабатывает 2YY10 строки рабочего листа. Если в столбце F строки находится положительное число, в столбец H помещается надпись ‘‘Выручка от сервиса’’, а все ячейки данной строки, расположенные в столбY цах A–H, выделяются зеленым цветом (рис. 5.8). Рис. 5.8. Пример использования цикла For...Next для обработки строк заданного диапазона Использование переменных в выражении For Предыдущий пример не очень практичен, поскольку он рассчитан на рабоY ту с фиксированным диапазоном ячеек. Для указания максимального значеY ния счетчика в выражении For рекомендуется использовать переменные, как показано ниже: FinalRow = Cells(65536, 1).End(xlUp).Row For i = 2 To FinalRow If Cells(i, 6).Value > 0 Then Cells(i, 8).Value = "Выручка от сервиса" Cells(i, 1).Resize(1, 8).Interior.ColorIndex = 4 End If Next I Использование переменных имеет определенные особенности, которые необходимо учитывать. Если импортированный файл счетов будет содержать только одну строку заголовка, значение переменной FinalRow окажется равY ным 1, а первая строка цикла примет вид For I = 2 to 1. Поскольку наY чальное значение счетчика больше его максимально допустимого значения, цикл будет пропущен и выполнение кода начнется со строки, следующей за строкой Next I. Изменение шага в цикле For...Next Цикл For...Next предусматривает возможность изменения значения пеY ременнойYсчетчика с шагом, отличным от 1. Рассмотрим задачу выделения цветом каждой второй строки в заданном диапазоне ячеек. Чтобы добиться этого, следует изменить шаг приращения значения переменнойYсчетчика, воспользовавшись оператором Step: FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow Step 2 Cells(I, 1).Resize(1, 8).Interior.ColorIndex = 35 Next I Циклы и управление выполнением кода Глава 5 145 В результате выполнения приведенного выше кода строки 2, 4, 6 и т.д. буY дут выделены бледноYзеленым цветом (рис. 5.9). Рис. 5.9. Пример изменения шага приращения значения переменной& счетчика цикла For...Next с помощью оператора Step Значение переменнойYсчетчика может изменяться практически с любым шагом. Ниже приведен пример извлечения каждой десятой строки (Step 10) из заданного диапазона ячеек: FinalRow = Cells(65536, 1).End(xlUp).Row NextRow = FinalRow + 5 Cells(NextRow - 1, 1).Value = "Выборка из приведенных выше данных" For I = 2 To FinalRow Step 10 Cells(I, 1).Resize(1, 8).Copy Destination:=Cells(NextRow, 1) NextRow = NextRow + 1 Next I Значение переменнойYсчетчика может изменяться и в направлении от большего к меньшему. В частности, это может пригодиться при выборочном удалении строк, как показано ниже: 'Удаление строк со значением S54 в столбце C. FinalRow = Cells(65536, 1).End(xlUp).Row For I = FinalRow To 2 Step -1 If Cells(I, 3).Value = "S54" Then Cells(I, 1).EntireRow.Delete End If Next I Досрочное завершение выполнения цикла Иногда выполнение цикла можно завершить досрочно. Рассмотрим задачу поиска строки, удовлетворяющей определенному критерию. Как только данY ная строка будет найдена, выполнение оставшейся части цикла теряет смысл. Для досрочного выхода из цикла применяется выражение Exit For. Следующий код используется для поиска строки с положительным числом в столбце F и нулем в столбце E. При нахождении такой строки выдается соY 146 Часть I Первые шаги общение об ошибке, а указатель помещается в ячейку проблемной строки, расположенную в столбце F: 'Следующий код используется для поиска ошибок в исходных данных. FinalRow = Cells(65536, 1).End(xlUp).Row ProblemFound = False For I = 2 To FinalRow If Cells(I, 6).Value > 0 Then If Cells(I, 5).Value = 0 Then Cells(I, 6).Select ProblemFound = True Exit For End If End If Next I If ProblemFound Then MsgBox "Ошибка в строке " & I Exit Sub End If Вложение циклов Цикл может выполняться внутри другого цикла. Одним из наиболее наглядY ных примеров вложения циклов является цикл, обрабатывающий строки в заY данном диапазоне ячеек, внутри которого выполняется цикл, обрабатывающий столбцы этих строк. Рассмотрим набор данных, представленный на рис. 5.10. Рис. 5.10. Вложение циклов позволяет реализовать последовательную обработку всех ячеек этого диапазона FinalRow = Cells(65536, 1).End(xlUp).Row FinalCol = Cells(1, 255).End(xlToLeft).Column For I = 2 To FinalRow 'Если номер строки - четный, начать с 1-го столбца. 'Если номер строки - нечетный, начать со 2-го столбца. If I Mod 2 = 1 Then StartCol = 1 Else StartCol = 2 End If For j = StartCol To FinalCol Step 2 Cells(I, j).Interior.ColorIndex = 35 Next j Next I Для обработки строк набора данных используется внешний цикл с переY меннойYсчетчиком I, а для обработки столбцов этих строк YYYY внутренний Циклы и управление выполнением кода Глава 5 147 цикл с переменнойYсчетчиком J. Поскольку набор данных состоит из 7 строк (см. рис. 5.10), внешний цикл проходит 7 итераций. Каждой итерации внешY него цикла соответствует 6 или 7 итераций внутреннего цикла (это зависит от номера обрабатываемой строки). Результат выполнения приведенного выше кода показан на рис. 5.11. Рис. 5.11. Результат выполнения вложенных циклов Циклы Do...Loop Существует несколько разновидностей цикла Do...Loop. Его наиболее проY стой вариант используется для выполнения большого числа однообразных операY ций. Рассмотрим задачу преобразования списка адресов, показанного на рис. 5.12. Рис. 5.12. Преобразование этого списка адресов в формат базы данных позволит автоматизировать процесс рассылки стандартных писем 148 Часть I Первые шаги Чтобы преобразовать подобный список адресов в формат базы данных (имя в столбце B, название улицы в столбце C, город и почтовый индекс в столбце D), можно записать макрос, включив режим относительных ссылок (см. главу 1, ‘‘Excel и VBA YYYY гремучая смесь’’). Предназначение макроса соY стоит в преобразовании в формат базы данных одного адреса и установке укаY зателя на ячейку, содержащую имя следующего адресата в списке: Sub Macro3() ' Макрос3 Макрос ' Макрос записан 29.01.2005 (Александр Журавлев) ' ' Преобразовать в формат базы данных один адрес. ' Установить указатель на ячейку, содержащую имя следующего ' адресата в списке. ' ' Сочетание клавиш: Ctrl+Shift+A ' Selection.Copy ActiveCell.Offset(0, 1).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(1, -1).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-1, 2).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(2, -2).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-2, 3).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(4, -3).Range("A1").Select End Sub См. также Об относительных ссылках рассказывается в разделе “Возможное решение: использование относительных ссылок” главы 1 на с. 52. Внимание Приведенный выше макрос не предназначен для профессионального применения и является примером решения единовременной задачи. С помощью макроса преобразование в формат базы данных одного адреса сводится к установке указателя на ячейку, содержащую имя адресата, и нажаY тию комбинации клавиш <Ctrl+Shift+A>. После копирования составляющих адреса в столбцы B, C и D указатель устанавливается на ячейку, содержащую имя следующего адресата в списке (рис. 5.13). Использование макроса позволяет преобразовывать список адресов в форY мат базы данных со скоростью 1 адрес в секунду. Однако эффективно ли данY ное решение при условии, что список состоит из 5000 адресов? Циклы и управление выполнением кода Глава 5 149 Рис. 5.13. После преобразования в формат базы данных одного адреса указатель устанавли& вается на ячейку, содержащую имя следующего адресата Поместив код макроса между выражениями Do и Loop, его можно выполY нять бесконечно. Таким образом, часы монотонной работы можно свести к нескольким минутам наблюдения за ходом выполнения макроса. Чтобы остановить выполнение макроса, следует воспользоваться комбиY нацией клавиш <Ctrl+Break>. Очевидно, подобное решение также не являетY ся оптимальным, поскольку оно все еще требует непосредственного участия человека. Sub Macro3() ' Макрос3 Макрос ' Макрос записан 29.01.2005 (Александр Журавлев) ' ' Преобразовать в формат базы данных один адрес. ' Установить указатель на ячейку, содержащую имя следующего ' адресата в списке. ' ' Сочетание клавиш: Ctrl+Shift+A ' Do Selection.Copy ActiveCell.Offset(0, 1).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(1, -1).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-1, 2).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(2, -2).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-2, 3).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(4, -3).Range("A1").Select Loop End Sub Приведенный выше цикл представляет собой компромиссное решение, направленное на быстрое выполнение поставленной задачи. К счастью, цикл Do...Loop поддерживает возможность своего досрочного завершения. Логичным условием выхода из приведенного выше цикла является достиY жение конца набора данных, признаком чего может служить выделение пусY той ячейки: 150 Часть I Первые шаги Sub Macro3() ' Макрос3 Макрос ' Макрос записан 29.01.2005 (Александр Журавлев) ' ' Преобразовать в формат базы данных один адрес. ' Установить указатель на ячейку, содержащую имя следующего ' адресата в списке. ' ' Сочетание клавиш: Ctrl+Shift+A ' Do If Not Selection.Value > "" then Exit Do Selection.Copy ActiveCell.Offset(0, 1).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(1, -1).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-1, 2).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(2, -2).Range("A1").Select Application.CutCopyMode = False Selection.Copy ActiveCell.Offset(-2, 3).Range("A1").Select ActiveSheet.Paste ActiveCell.Offset(4, -3).Range("A1").Select Loop End Sub Использование операторов While и Until Операторы While и Until могут использоваться как в выражении Do, так и в выражении Loop. Единственным обязательным условием является налиY чие некоторого критерия, принимающего значение True или False. При использовании конструкции Do While <критерий>...Loop цикл не выполняется, если <критерий> равен False. К примеру, при чтении соY держимого текстового файла цикл не должен выполняться, если был достигнут конец файла (об этом свидетельствует значение функции EOF, равное True): 'Считать содержимое текстового файла, 'за исключением итоговых строк. Open "C:\Счет.txt" For Input As #1 r = 1 Do While Not EOF(1) Line Input #FileNumber, Data If Not Left(Data, 5) = "ВСЕГО" Then 'Импортировать строку. r = r + 1 Cells(r, 1).Value = Data End If Loop Close #1 В приведенном выше коде было использовано ключевое слово NOT. При досY тижении конца файла значение функции EOF(1) становится равным True. НеY Циклы и управление выполнением кода Глава 5 которые программисты считают, что частое использование ключевых слов NOT затрудняет восприятие кода. Чтобы избавиться от NOT, можно воспользоваться альтернативной конструкцией Do Until <критерий>...Loop: 'Считать содержимое текстового файла, 'за исключением итоговых строк. Open "C:\Счет.txt" For Input As #1 r = 1 Do Until EOF(1) Line Input #FileNumber, Data If Not Left(Data, 5) = "ВСЕГО" Then 'Импортировать строку. r = r + 1 Cells(r, 1).Value = Data End If Loop Close #1 Иногда цикл необходимо выполнить хотя бы один раз. Для этого оператоY ры While и Until помещают в конец цикла, в выражение Loop. В результате выполнения приведенного ниже кода пользователю предлагается ввести некоY торое число (сумму, указанную в счете) до тех пор, пока он не введет 0: TotalSales = 0 Do x = InputBox(Prompt:="Введите сумму следующего счета _ или 0 для завершения.") TotalSales = TotalSales + x Loop Until x = 0 MsgBox "Общая сумма сегодняшних продаж составила $" & TotalSales В следующем примере пользователю предлагается ввести сумму, указанY ную в чеке. Оплата нескольких счетов одним чеком — весьма распространенY ная практика. Макрос последовательно ‘‘погашает’’ счета (начиная с самых ранних) до тех пор, пока не исчерпает сумму чека. 'Ввести сумму, указанную в чеке. AmtToApply = InputBox("Введите сумму, указанную в чеке") 'Погасить счета, начиная с самых ранних, 'уменьшая при этом значение переменной AmtToApply. NextRow = 2 Do While AmtToApply > 0 OpenAmt = Cells(NextRow, 3) If OpenAmt > AmtToApply Then 'Погасить счет с помощью чека. Cells(NextRow, 4).Value = AmtToApply AmtToApply = 0 Else Cells(NextRow, 4).Value = OpenAmt AmtToApply = AmtToApply - OpenAmt End If NextRow = NextRow + 1 Loop Возможность использования операторов While и Until как в начале, так и в конце конструкции Do...Loop, позволяет осуществлять тонкий контроль над ходом выполнения цикла. 151 152 Часть I Первые шаги Цикл While...Wend Цикл While...Wend был включен в VBA для обеспечения обратной соY вместимости. В справочной системе VBA Microsoft рекомендует использоY вать циклы Do...Loop как обладающие более широкими возможностями. Чтобы объяснить принцип работы цикла While...Wend, приведем неY большой пример. 'Считывание информации о счетах и вычисление общей суммы продаж. Open "C:\Счет.txt" For Input As #1 TotalSales = 0 While Not EOF(1) Line Input #1, Data TotalSales = TotalSales + Data Wend MsgBox "Общая сумма продаж = " & TotalSales Close #1 Первой строкой цикла While...Wend всегда является строка While <критерий>, последней YYYY строка Wend. Возможность досрочного выхода из цикла не предусмотрена. За счет наличия операторов While и Until, котоY рые могут применяться как в выражении Do, так и в выражении Loop, а также возможности досрочного выхода из цикла, конструкция Do...Loop заслуженно считается более надежной и гибкой, нежели конструкция While...Wend. Цикл For Each...Next Цикл For Each...Next является одним из наиболее полезных циклов объектноYориентированного языка программирования. К сожалению, этот цикл не поддерживается средством записи макросов. Рабочая книга Excel переполнена всевозможными коллекциями объекY тов YYYY рабочие листы в рабочей книге, ячейки в диапазоне, сводные таблицы на рабочем листе, последовательности данных на диаграмме и т.п. Цикл For Each...Next предназначен для последовательной обработки элементов колY лекции. Прежде чем перейти к его более подробному изучению, рассмотрим понятие объектной переменной. Объектные переменные Обычные переменные хранят только одно значение. В отличие от них, объект* ные переменные хранят много значений, которые являются значениями свойств соответствующего объекта. Существует мнение, согласно которому все переменные, используемые в процедуре, необходимо объявлять в ее начале с помощью ключевого слова Dim. Это позволяет указать тип переменной, например Integer или Double. Несмотря на то что таким образом удается сэкономить немного оперативной памяти, вам следует заранее знать весь список переменных, которые вы собиY Циклы и управление выполнением кода Глава 5 153 раетесь использовать в процедуре. В отличие от обычных переменных, объявY ление объектных переменных имеет множество преимуществ, одним из котоY рых является возможность автоматического завершения ввода. Следующий код содержит объявления трех объектных переменных, соответствующих раY бочему листу, диапазону ячеек и сводной таблице: Sub Test() Dim WSD As Worksheet Dim MyCell As Range Dim PT As PivotTable Set WSD = ThisWorkbook.Worksheets("Данные") Set MyCell = WSD.Cells(65536, 1).End(xlUp).Offset(1, 0) Set PT = WSD.PivotTables(1) ... Чтобы присвоить значение объектной переменной, помимо знака равенстY ва следует воспользоваться ключевым словом Set, как показано выше. Одним из наиболее существенных преимуществ использования объектных переменных является возможность быстрого обращения к нужному объекту, например, WSD вместо ThisWorkbook.Worksheets("Данные"). Кроме того, как уже отмечалось выше, объектная переменная предоставляY ет доступ ко всем свойствам соответствующего объекта. Вместо переменнойYсчетчика в цикле For Each...Next используется объектная переменная. В приведенном ниже коде такой переменной является переменная Cell: For Each Cell In Range("A1").CurrentRegion.Resize(, 1) If Left(Cell.Value, 5) = "Всего" Then Cell.Resize(1, 8).Font.Bold = True End If Next Cell Свойство CurrentRegion используется для выделения непрерывного диаY пазона ячеек, а свойство Resize — для ограничения диапазона столбцом A. С помощью следующего кода производится поиск рабочего листа с заданY ным именем в коллекции всех открытых рабочих книг: For Each wb in Workbooks If wb.Worksheet(1).Name = "Menu" Then WBFound = True WBName = wb.Name End If Next wb В результате выполнения приведенного ниже кода с текущего рабочего листа будут удалены все находящиеся на нем фигуры: For Each Sh In ActiveSheet.Shapes Sh.Delete Next Sh Выполнение следующего кода приведет к удалению с текущего рабочего листа всех сводных таблиц: For Each PT In ActiveSheet.PivotTables PT.TableRange2.Clear Next PT 154 Часть I Первые шаги Обработка всех файлов в папке Рассмотрим несколько полезных процедур, построенных на применении циклов. Первая процедура использует объект VBA FileSearch для нахождения всех JPG& файлов в указанной папке и вывода их списка на рабочем листе Excel. Внешний цикл, использующий переменную&счетчик I, обрабатывает список най& денных файлов. На каждой итерации цикла полное имя файла помещается в пе& ременную ThisEntry. Чтобы отделить путь к файлу от его имени, применяется внутренний цикл, использующий переменную&счетчик J. Sub ListJpgFiles() 'Этот макрос находит все JPG-файлы в заданной 'папке и выводит их список на рабочем листе Excel. 'Очистить все ячейки рабочего листа. Cells.Clear 'Создать заголовки столбцов. Range("A1:D1").Value = Array("FileName", "Path", _ "FileName", "NewPath") NextRow = 2 'Поиск файлов осуществляется с помощью объекта FileSearch. With Application.FileSearch .NewSearch .LookIn = "C:\" .SearchSubFolders = True .Filename = "*.jpg" .Execute FilesToProcess = .FoundFiles.Count 'Обработать список найденных файлов. For I = 1 To .FoundFiles.Count ThisEntry = .FoundFiles(I) Cells(NextRow, 1).Value = ThisEntry 'Отделить путь к файлу от его имени. For j = Len(ThisEntry) To 1 Step -1 If Mid(ThisEntry, j, 1) = _ Application.PathSeparator Then Cells(NextRow, 2) = Left(ThisEntry, j) Cells(NextRow, 3) = Mid(ThisEntry, j + 1) Exit For End If Next j NextRow = NextRow + 1 Next I End With End Sub Приведенная выше процедура может быть использована для перемещения JPG& файлов. Введите в столбце D полный путь к папке, в которую необходимо пере& местить соответствующий файл. На каждой итерации приведенного ниже цикла For Each...Next объектная переменная Cell содержит ссылку на ячейку в столбце A (исходное имя файла), а выражение Cell.Offset(0, 3) — ссылку на соответствующую ячейку в столбце D (полный путь к папке, в которую необходимо переместить файл). Циклы и управление выполнением кода Глава 5 155 Sub CopyToNewFolder() FinalRow = Range("A65536").End(xlUp).Row For Each Cell In Range("A2:A" & FinalRow) OrigFile = Cell.Value If Cell.Offset(0, 3).Value > "" Then NewFile = Cell.Offset(0, 3) & _ Application.PathSeparator & Cell.Offset(0, 2) FileCopy OrigFile, NewFile End If Next Cell End Sub Управление выполнением кода: использование конструкций If...Then...Else и Select Case Управление выполнением кода YYYY это еще один фундаментальный аспект программирования, игнорируемый средством записи макросов. VBA поддерY живает две конструкции, реализующие концепцию управления выполнением кода, — If...Then...Else и Select Case. Знакомство с конструкцией If...Then...Else Краеугольным камнем концепции управления выполнением кода является выражение If. Рассмотрим задачу копирования списка продуктов, показанY ного на рис. 5.14, в два списка YYYY ‘‘Фрукты’’ и ‘‘Овощи’’. Рис. 5.14. Задача разделения спи& ска продуктов на два списка мо& жет быть решена с помощью од& ного цикла 156 Часть I Первые шаги Начинающему программисту может придти в голову идея создания двух циклов YYYY по одному для составления каждого списка. Тем не менее, данная задача решается с помощью всего лишь одного цикла и конструкции If...Then...Else. Условие Обязательной частью выражения If является условие, имеющее значение True или False. Ниже приведены примеры простых и сложных условий: If Range("A1").Value = "Товар"; If Not Range("A1").Value = "Товар"; If Range("A1").Value = "Товар" And Range("B1").Value = "Фрукт"; If Range("A1").Value = "Товар" Or Range("B1").Value = "Фрукт". Конструкция If...Then...End If Строки кода, размещенные после выражения If, будут выполнены только при соблюдении указанного условия. Чтобы завершить блок If, следует восY пользоваться выражением End If, как показано ниже: Sub ColorFruitRedBold() FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow If Cells(I, 1).Value = "Фрукт" Then Cells(I, 1).Resize(1, 3).Font.Bold = True Cells(I, 1).Resize(1, 3).Font.ColorIndex = 3 End If Next I MsgBox "Все фрукты выделены красным цветом и утолщением _ шрифта" End Sub Конструкция If...Then...Else...End If Иногда необходимо выполнить один фрагмент кода, если условие равно True, и другой YYYY если оно равно False. В VBA для этого следует указать втоY рой фрагмент кода после ключевого слова Else. Для завершения блока If...Then...Else используется выражение End If: Sub FruitRedVegGreen() FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow If Cells(I, 1).Value = "Фрукт" Then Cells(I, 1).Resize(1, 3).Font.ColorIndex = 3 Else Cells(I, 1).Resize(1, 3).Font.ColorIndex = 50 Циклы и управление выполнением кода Глава 5 End If Next I MsgBox "Все фрукты выделены красным цветом, _ а все овощи - изумрудным" End Sub Конструкция If...ElseIf...End If Структура If...End If поддерживает возможность проверки нескольких условий с помощью ключевого слова ElseIf. Как показано на рис. 5.14, список продуктов содержит одно травянистое растение, что наводит на мысль о необходимости проверки трех условий — является ли элемент списка фруктом, овощем или травянистым растением? При негативном результате всех трех проверок можно сделать вывод, что элемент списка содержит ошибку: Sub MultipleIf() FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow If Cells(I, 1).Value = "Фрукт" Then Cells(I, 1).Resize(1, 3).Font.ColorIndex = 3 ElseIf Cells(I, 1).Value = "Овощ" Then Cells(I, 1).Resize(1, 3).Font.ColorIndex = 50 ElseIf Cells(I, 1).Value = "Растение" Then Cells(I, 1).Resize(1, 3).Font.ColorIndex = 5 Else 'Элемент списка содержит ошибку. Cells(I, 1).Resize(1, 3).Interior.ColorIndex = 6 End If Next I MsgBox "Фрукты выделены красным цветом, овощи - изумрудным, _ а травянистые растения - синим" End Sub Конструкция Select Case...End Select Когда условий становится слишком много, использование структуры If...ElseIf теряет свою привлекательность. Для таких случаев VBA распоY лагает конструкцией Select Case, первая строка которой содержит так наY зываемое условное выражение: Select Case Cells(I, 1).Value После строки с условным выражением перечислены его возможные значеY ния, записанные после ключевого слова Case. Для каждого значения должен быть указан фрагмент кода, который будет выполнен, если условное выражеY ние примет это значение. Если необходимо предусмотреть возможность принятия условным выY ражением значения, отличного от всех перечисленных, воспользуйтесь ключевыми словами Case Else. Завершает блок Select Case выражеY ние End Select. 157 158 Часть I Первые шаги Единственное отличие следующего кода от приведенного ранее заключаетY ся в использовании конструкции Case Select вместо конструкции If...ElseIf: Sub SelectCase() FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow Select Case Cells(I, 1).Value Case "Fruit" Cells(I, 1).Resize(1, 3).Font.ColorIndex = 3 Case "Vegetable" Cells(I, 1).Resize(1, 3).Font.ColorIndex = 50 Case "Herbs" Cells(I, 1).Resize(1, 3).Font.ColorIndex = 5 Case Else End Select Next I MsgBox "Фрукты выделены красным цветом, овощи - изумрудным, _ а травянистые растения - синим" End Sub Использование сложных выражений Case Выражения Case могут быть как простыми (рассматривались в предыY дущем разделе), так и сложными. Следующее выражение определяет единое действие для ячеек, содержащих значения ‘‘Клубника’’, ‘‘Голубика’’ и ‘‘Малина’’: Case "Клубника", "Голубика", "Малина" AdCode = 1 В качестве значения условного выражения можно указать диапазон, как показано ниже: Case 1 To 20 Discount = 0.05 Case 21 To 100 Discount = 0.1 Кроме того, воспользовавшись ключевым словом Is и оператором сравнеY ния (например, > или <), можно задать значение условного выражения как открытый диапазон: Case Is < 10 Discount = 0 Case Is > 100 Discount = 0.2 Case Else Discount = 0 Вложение выражений If Выражение If может находиться внутри другого выражения If. Вложение выражений If требует от программиста аккуратности при оформлении проY граммного кода. Поскольку в конце подобных конструкций скапливается неY Циклы и управление выполнением кода Глава 5 159 сколько строк End If, соблюдение отступов поможет определить, к какому выражению If относится та или иная строка End If. Итоговый макрос содержит большое количество правил, описывающих политику скидок. Если объем заказа фруктов составляет менее 5 ящиков, скидка не преY доставляется. Если объем заказа фруктов составляет от 5 до 20 ящиков, предоставляY ется скидка в размере 10%. Если объем заказа фруктов составляет более 20 ящиков, предоставляетY ся скидка в размере 15%. Если объем заказа травянистых растений составляет менее 10 ящиков, скидка не предоставляется. Если объем заказа травянистых растений составляет от 10 до 15 ящиков, предоставляется скидка в размере 3%. Если объем заказа травянистых растений составляет более 15 ящиков, предоставляется скидка в размере 6%. Если объем заказа овощей, за исключением спаржи, составляет 5 ящиков и более, предоставляется скидка в размере 12%. Если объем заказа спаржи составляет 20 ящиков и более, предоставляY ется скидка в размере 12%. Во время распродажи продукта на него предоставляется скидка в разY мере 25%. Никакие другие скидки при этом не предоставляются. На этой неделе проводится распродажа клубники, салата и помидоров. Ниже приведен код, реализующий указанную политику скидок. Sub ComplexIf() FinalRow = Cells(65536, 1).End(xlUp).Row For I = 2 To FinalRow ThisClass = Cells(I, 1).Value ThisProduct = Cells(I, 2).Value ThisQty = Cells(I, 3).Value 'Определение продуктов, находящихся на распродаже. Select Case ThisProduct Case "Клубника", "Салат", "Помидоры" Sale = True Case Else Sale = False End Select 'Расчет скидки. If Sale Then Discount = 0.25 Else If ThisClass = "Фрукт" Then Select Case ThisQty Case Is < 5 160 Часть I Первые шаги Discount = 0 Case 5 To 20 Discount = 0.1 Case Is > 20 Discount = 0.15 End Select ElseIf ThisClass = "Растение" Then Select Case ThisQty Case Is < 10 Discount = 0 Case 10 To 15 Discount = 0.03 Case Is > 15 Discount = 0.05 End Select ElseIf ThisClass = "Овощ" Then 'Расчет скидки на спаржу. If ThisProduct = "Спаржа" Then If ThisQty < 20 Then Discount = 0 Else Discount = 0.12 End If Else If ThisQty < 5 Then Discount = 0 Else Discount = 0.12 End If End If 'Является ли продукт спаржей? End If 'Является ли продукт овощем? End If 'Проводится ли распродажа продукта? Cells(I, 4).Value = Discount If Sale Then Cells(I, 4).Font.Bold = True End If Next I Range("D1").Value = "Скидка" MsgBox "Расчет скидок завершен" End Sub Следующий шаг Цикл YYYY это фундаментальный компонент любого языка программирования. VBA поддерживает как традиционные циклы For...Next и Do...Loop, так и цикл For Each...Next, характерный для объектноYориентированных языY ков. В следующей главе будет рассмотрен очень важный для Excel VBA стиль записи ссылок с таинственным названием R1C1. Глава 6 Ñòèëü çàïèñè ññûëîê R1C1 6 Сравнение стилей записи ссылок A1 и R1C1 Стиль записи ссылок A1 берет свое начало от приложения VisiCalc. Для обращения к ячейке, располоY женной в верхнем левом углу элекY тронной таблицы, Дэн Бриклин (Dan Bricklin) и Боб Фрэнкстон (Bob Frankston) предложили использовать запись ‘‘A1’’. Аналогичную адресную схему взял на вооружение Мич КейY пор (Mitch Kapor) в своем легендарY ном продукте Lotus 1Y2Y3. Вскоре заY конодательницей мод попыталась стать Microsoft, предложив рынку программу Multiplan и стиль записи ссылок R1C1. Согласно этому стилю для обращения к ячейке, располоY женной в верхнем левом углу элекY тронной таблицы, использовалась запись ‘‘R1C1’’, указывающая на то, что ячейка находится в 1Yй строке (Row) 1Yго столбца (Column). Благодаря лидирующему положеY нию, которое занимал на рынке проY дукт Lotus 1Y2Y3 в 1980Yх и начале 1990Yх годов, стиль записи ссылок A1 стал общепризнанным стандартом. Осознав всю невыгодность своей стратегии, Microsoft добавила в Excel поддержку адресации A1, сделав ее используемой по умолчанию. СледуY ет отметить, что официально MicroY soft поддерживает оба способа записи ссылок. Сравнение стилей записи ссылок A1 и R1C1.............................161 Использование стиля ссылок R1C1 в Excel ..................................... 162 Чудесный мир формул Excel ..... 163 Ссылки в стиле R1C1 ..................... 166 Использование ссылок в стиле R1C1 при условном форматировании ячеек...............171 Использование ссылок в стиле R1C1 при создании формулы массива........................................... 174 Следующий шаг............................ 175 162 Часть I Первые шаги R1C1 — дела давно минувших дней? Подавляющее большинство пользователей Excel единогласны во мнении, что стиль записи ссылок R1C1 давно утратил свою актуальность. К сожалеY нию, именно этот стиль ‘‘пришелся по душе’’ средству записи макросов. ТаY ким образом, на первый взгляд знание адресной схемы R1C1 вызвано необхоY димостью уметь ‘‘читать’’ автоматически сгенерированный код. R1C1 — сильные стороны И все же необходимо отдать должное Microsoft. Познакомившись с R1C1Y формулами, вы поймете, что они намного полезнее обычных формул (особенно ярко это проявляется при использовании формул в VBA). ПримеY нение ссылок в стиле R1C1 позволяет создавать более эффективный код. Кроме того, употребление адресации R1C1 является обязательным требованиY ем при создании формул массивов и при задании условного форматирования ячеек. Именно последнее и обуславливает необходимость ознакомления с таY инственным стилем записи ссылок R1C1. Использование стиля ссылок R1C1 в Excel Чтобы указать на необходимость использования в Excel адресации R1C1, выберите команду главного меню Сервис Параметры (Tools Options), пеY рейдите во вкладку Общие (General) и установите флажок Стиль ссылок R1C1 (R1C1 reference style) (рис. 6.1). Рис. 6.1. Чтобы выбрать адресацию R1C1, установите флажок Стиль ссылок R1C1 на вкладке Общие диалогового окна Параметры Стиль записи ссылок R1C1 Глава 6 163 После перехода к стилю ссылок R1C1 буквы в заголовках столбцов (A, B, C и т.д.) будут заменены цифрами (1, 2, 3 и т.д.), как показано на рис. 6.2. Рис. 6.2. После перехода к стилю ссылок R1C1 буквы в заголовках столбцов будут заменены цифрами В соответствии с новой адресацией ячейка B5 будет называться R5C2, поY скольку она расположена в 5Yй строке 2Yго столбца. Чудесный мир формул Excel Возможность автоматического пересчета значений тысяч ячеек стала осY новным преимуществом электронных таблиц перед перфокартами, испольY зуемыми вплоть до 1979 года. В наши дни одной из наиболее востребованных функций электронных таблиц является функция автоматического копироваY ния формулы из одной ячейки в другую. Как “размножаются” формулы Рассмотрим достаточно простую электронную таблицу, показанную на рис. 6.3. Рис. 6.3. С помощью маркера заполнения формула может быть скопирована из одной ячейки в другую Введите в ячейку D4 формулу =C4*B4 и с помощью маркера заполнения скопируйте ее в ячейки D5:D9. 164 Часть I Первые шаги Формула в ячейке F4 включает в себя как абсолютные, так и относительY ные ссылки: =ЕСЛИ(E4;ОКРУГЛ(D4*$B$1;2);0) (=IF(E4,ROUND(D4*$B$1, 2),0)). Благодаря наличию знаков доллара ($) эта формула всегда вычисляет произведение общей цены товара, находящейся в столбце D текущей строки, на величину налога, внесенную в ячейку B1. Результат вычисления значений ячеек, показанный на рис. 6.4, был достигнут с помощью формул, показанных на рис. 6.5. (Чтобы переключиться в режим отоY бражения формул в Excel, воспользуйтесь комбинацией клавиш <Ctrl+~>.) Рис. 6.4. Результат вычисления значений ячеек в столбцах D, F и G достигнут с помощью формул, показанных на рис. 6.5 Рис. 6.5. Чтобы переключиться в режим отображения формул в Excel, воспользуйтесь комбинацией клавиш <Ctrl+~>. Возможность автоматического копирования форму& лы из одной ячейки в другую поистине удивительна! Обратите внимание, что для достижения показанного на рис. 6.5 результаY та необходимо ввести вручную всего лишь 4 формулы: 3 в ячейки D4, F4, G4 и 1 в ячейку G10. В остальные ячейки формулы могут быть скопированы авY томатически. Превращение формулы =F4+D4 в ячейке G4 при ее копировании в ячейку G5 в формулу =F5+D5 приводит начинающих пользователей Excel в настоящий восторг! Разоблачение На самом деле Excel оперирует только R1C1Yформулами. Поддержка формул в стиле A1 для совместимости со стандартом VisiCalc и Lotus реализоY вана исключительно на уровне интерфейса Excel. Стиль записи ссылок R1C1 Глава 6 165 Перейдя к стилю ссылок R1C1, можно заметить, что ‘‘различные’’ формуY лы в ячейках D4:D9 на самом деле являются одной и той же R1C1Yформулой. Это же справедливо и по отношению к ячейкам F4:F9 и G4:G9 (рис. 6.6). Рис. 6.6. Переход к стилю ссылок R1C1 дает неожиданный результат — все формулы, расположенные в столбцах 4 и 6, одинаковые Поскольку поддержка формул в стиле A1 реализована исключительно на уровY не интерфейса Excel, становится ясно, что автоматическое копирование формулы из одной ячейки в другую отнюдь не является чемYто экстраординарным. Поддержка ссылок в стиле R1C1 делает возможным более эффективное применение формул в макросах VBA. В частности, это позволяет ввести одну и ту же формулу в целый диапазон ячеек с помощью одной строки кода. Практикум Использование ссылок в стиле A1 и R1C1 в VBA Заполним ячейки рассмотренной выше таблицы с помощью VBA, используя ссыл& ки в стиле A1. Прежде всего, введем формулы в ячейки D4, F4 и G4. Далее, скопи& руем их и вставим в требуемые ячейки, как показано ниже: Sub BookA1Style() ' Нахождение последней строки с данными. FinalRow = Range("B65536").End(xlUp).Row ' Ввод формул. Range("D4").Formula = "=B4*C4" ' В англоязычной версии Excel: ' Range("F4").Formula = "=IF(E4,ROUND(D4*$B$1,2),0)" Range("F4").FormulaLocal = "=ЕСЛИ(E4;ОКРУГЛ(D4*$B$1;2);0)" Range("G4").Formula = "=F4+D4" ' Копирование формул в строке 4 в требуемые ячейки. Range("D4").Copy Destination:=Range("D5:D" & FinalRow) Range("F4:G4").Copy Destination:=Range("F5:G" & FinalRow) ' Формирование итоговой строки. Cells(FinalRow + 1, 1).Value = "Всего" ' В англоязычной версии Excel: ' Cells(FinalRow + 1, 6).Formula = "=SUM(G4:G" & FinalRow & ")" Cells(FinalRow + 1, 6).FormulaLocal = "=СУММ(G4:G" & _ FinalRow & ")" End Sub 166 Часть I Первые шаги Как видим, понадобилось 3 строки для ввода формул и еще 2 для их копирования в требуемые ячейки. Эквивалентный код, в котором используются ссылки в стиле R1C1, позволяет вве& сти формулу сразу в целый диапазон ячеек с помощью одной строки кода, как по& казано ниже: Sub BookR1C1Style() ' Нахождение последней строки с данными. FinalRow = Range("B65536").End(xlUp).Row ' Ввод формул. Range("D4:D" & FinalRow).FormulaR1C1 = "=RC[-1]*RC[-2]" ' В англоязычной версии Excel: ' Range("F4:F" & FinalRow).FormulaR1C1 = _ "=IF(RC[-1],ROUND(RC[-2]*R1C2,2),0)" Range("F4:F" & FinalRow).FormulaR1C1Local = _ "=ЕСЛИ(RC[-1];ОКРУГЛ(RC[-2]*R1C2;2);0)" Range("G4:G" & FinalRow).FormulaR1C1 = "=+RC[-1]+RC[-3]" ' Формирование итоговой строки. Cells(FinalRow + 1, 1).Value = "Всего" ' В англоязычной версии Excel: ' Cells(FinalRow + 1, 6).Formula = "=SUM(G4:G" & FinalRow & ")" Cells(FinalRow + 1, 6).FormulaLocal = _ "=СУММ(G4:G" & FinalRow & ")"End Sub Ссылки в стиле R1C1 Согласно стилю R1C1, обращение к ячейке осуществляется по номеру ее строки (R) и столбца (С). Подробное рассмотрение ссылок в стиле R1C1 начY нем с относительных ссылок как наиболее часто используемых в формулах на рабочем листе и в VBA. Относительные ссылки в стиле R1C1 Рассмотрим задачу обращения к ячейке в формуле Excel. Чтобы определить адрес ячейки, следует задать смещение по строкам и столбцам в квадратных скобках после букв ‘‘R’’ и ‘‘C’’, соответственно. Положительное смещение по столбцам обозначает смещение вправо на указанное число позиций, а отрицательное смещение по столбцам YYYY смещеY ние влево на указанное число позиций. К примеру, адрес ячейки F5 относиY тельно ячейки E5 выглядит как RC[1], а адрес ячейки D5 относительно ячейY ки E5 — как RC[-1]. Положительное смещение по строкам обозначает смещение вниз на укаY занное число позиций, а отрицательное смещение по строкам YYYY смещение вверх на указанное число позиций. К примеру, адрес ячейки E6 относительно ячейки E5 выглядит как R[1]C, а адрес ячейки E4 относительно ячейки E5 — как R[-1]C. Нулевое смещение по строкам или столбцам (обозначается отсутствием соответствующих квадратных скобок) используется для обращения к ячейкам, Стиль записи ссылок R1C1 Глава 6 167 расположенным в той же строке или столбце, что и ячейка с формулой. РасY смотрим несколько примеров. Чтобы обратиться к ячейке D4 из ячейки E5, следует воспользоваться формулой =R[-1]C[-1]. Чтобы обратиться к ячейке D5 из ячейки E5, следует воспользоваться формулой =RC[-1]. Чтобы обратиться к ячейке F5 из ячейки E5, следует воспользоваться формулой =RC[1]. Чтобы обратиться к ячейке E5 из ячейки E5, следует воспользоваться формулой =RC. Последняя формула определяет так называемую цикY лическую ссылку и, как правило, никогда не применяется на практике. Более наглядно обращение к различным ячейкам из ячейки E5 продемонY стрировано на рис. 6.7. Рис. 6.7. Пример использования относительных ссылок в стиле R1C1 Относительные ссылки в стиле R1C1 можно применять и для обращения к диапазону ячеек. К примеру, следующая формула суммирует значение 12 ячеек, расположенных слева от ячейки с формулой: =СУММ(RC[-12]:RC[-1]) (В англоязычной версии Excel эта формула записывается как =SUM(RC[12]:RC[-1]).) Абсолютные ссылки в стиле R1C1 Абсолютная ссылка всегда указывает на ячейку, расположенную в опредеY ленном месте. При использовании адресации в стиле A1 абсолютные ссылки задаются путем предварения буквы столбца и номера строки знаком доллара ($), например $B$2. При использовании адресации в стиле R1C1 абсолютные ссылки задаются путем опускания квадратных скобок, например R2C2. Смешанные ссылки в стиле R1C1 Смешанная ссылка содержит либо абсолютный столбец и относительную строку, либо абсолютную строку и относительный столбец. 168 Часть I Первые шаги Рассмотрим макрос, импортирующий в Excel файл Счет.txt. Адрес итоY говой строки вычисляется с помощью выражения .End(xlUp). В определенY ные ячейки итоговой строки необходимо поместить сумму значений всех ячеек, расположенных выше ячейки с формулой вплоть до 2Yй строки включительно: Sub MixedReference() TotalRow = Cells(65536, 1).End(xlUp).Row + 1 Cells(TotalRow, 1).Value = "Всего" ' В англоязычной версии Excel: ' Cells(TotalRow, 5).Resize(1, 3).FormulaR1C1 = _ '"=SUM(R2C:R[-1]C)" Cells(TotalRow, 5).Resize(1, 3).FormulaR1C1Local = _ "=СУММ(R2C:R[-1]C)" End Sub Как показано в приведенном выше коде, упомянутый диапазон ячеек задаY ется как R2C:R[-1]C. Таким образом, единственная R1C1Yформула может быть использована для обращения к диапазону ячеек неопределенного размеY ра, что еще раз подтверждает эффективность использования ссылок в стиле R1C1 (рис. 6.8). Рис. 6.8. Пример использования смешанной ссылки в стиле R1C1 Обращение к строке или столбцу с помощью ссылок в стиле R1C1 Рассмотрим задачу нахождения максимального значения в столбце G. ПоY скольку точное число строк с данными заранее неизвестно, для этого можно применить A1Yформулу =МАКС($G:$G) (=MAX($G:$G)) или R1C1Yформулу =МАКС(C7) (=MAX(C7)). Аналогично, для того чтобы найти минимальное значение в строке 1, можно применить A1Yформулу =МИН($1:$1) (=MIN($1:$1)) или R1C1Yформулу =МИН(R1) (=MIN(R1)). Обратиться к строке или столбцу можно также с помощью относительной ссылки в стиле R1C1. Например, чтобы найти среднее всех значений в строке, расположенной над текущей ячейкой, можно воспользоваться формулой =СРЗНАЧ(R[-1]) (=AVERAGE(R[-1])). Стиль записи ссылок R1C1 Глава 6 169 Замена нескольких A1Lформул одной R1C1Lформулой Формулы R1C1 намного эффективнее A1Yформул. Рассмотрим классичеY ский пример построения таблицы умножения с помощью однойYединственной формулы, использующей смешанные ссылки в стиле R1C1. Построение таблицы умножения с помощью единственной R1C1Lформулы Введите числа от 1 до 12 в ячейки B1:M1. Выделите эти ячейки и скопируйY те их с применением транспонирования в ячейки A2:A13. Создадим формулу, вычисляющую для каждой ячейки из диапазона B2:M13 произведение соотY ветствующих чисел из 1Yго столбца и 1Yй строки. Естественно, здесь не обойY тись без ссылок в стиле R1C1: Sub MultiplicationTable() ' Построение таблицы умножения с помощью одной R1C1-формулы. Range("B1:M1").Value = Array(1, 2, 3, 4, 5, 6, 7, _ 8, 9, 10, 11, 12) Range("B1:M1").Font.Bold = True Range("B1:M1").Copy Range("A2:A13").PasteSpecial Transpose:=True Range("B2:M13").FormulaR1C1 = "=RC1*R1C" Cells.EntireColumn.AutoFit End Sub Формула =RC1*R1C проста до гениальности. Результатом выполнения приY веденного выше кода является таблица умножения, показанная на рис. 6.9. Рис. 6.9. Таблица умножения, построенная с по& мощью формулы =RC1*R1C Внимание Обратите внимание, что после выполнения макроса диапазон ячеек B1:M1 остается активным элементом буфера обмена. Если нажать клавишу <Enter>, содержимое этого диапазона будет скопировано в текущую выделенную область рабочего листа, что, вообще говоря, нежелательно. Чтобы выйти из режима копирования/вставки, добавьте в конце макроса строку Application.CutCopyMode = False. 170 Часть I Первые шаги Интересный факт Проведем небольшой эксперимент. Выделите ячейку F6 и начните запись нового макроса, выбрав в меню Excel команду Сервис Макрос Начать запись (Tools Macro Record New Macro). Щелкните на кнопке Относительная ссылка (Relative Reference), расположенной на панели инструментов Остановить запись (Stop Recording). Введите формулу =A1 и нажмите комбиY нацию клавиш <Ctrl+Enter>, чтобы остаться в ячейке F6. Щелкните на кнопY ке Остановить запись, расположенной на одноименной панели инструменY тов, чтобы остановить запись макроса. Код, сгенерированный средством записи макросов, фактически будет представлять собой одну формулу, ссылающуюся на ячейку, расположенную на 5 строк выше и на 5 столбцов левее текущей активной ячейки: Sub PointFiveRowsUp() ActiveCell.FormulaR1C1 = "=R[-5]C[-5]" End Sub А теперь выделите ячейку A1 и выполните только что созданный макрос. Вероятно, вы ожидаете получить сообщение о распространенной ошибке вреY мени выполнения 1004? Как бы не так! В результате выполнения макроса формула в ячейке A1 будет ссылаться на ячейку IR65532, что фактически озY начает переход из левой верхней в правую нижнюю часть рабочего листа. НеY смотря на то, что этот весьма любопытный факт не имеет какойYлибо практиY ческой ценности, его знание может пригодиться при написании программY ного кода. Тренируем память Преимущество использования R1C1Yформул в коде VBA частично нивелиY руется необходимостью переключения интерфейса Excel в режим ссылок в стиле R1C1. Многие пользователи предпочитают не делать этого и стараются запомнить соответствие между буквами, применяемыми в качестве заголовка столбцов, и порядковыми номерами последних. К сожалению, запомнить, что буква ‘‘U’’ является 21Yй буквой латинского алфавита, получается далеко не сразу. Следующая игра поможет вам быстрее натренировать свою память: Sub QuizColumnNumbers() Do i = Int(Rnd() * 26) + 1 Ans = InputBox("Заголовком какого столбца является _ буква " & Chr(64 + i) & "?") If Ans = "" Then Exit Do If Not (Ans + 0) = i Then MsgBox "Буква " & Chr(64 + i) & " является _ заголовком столбца # " & i End If Loop End Sub Стиль записи ссылок R1C1 Глава 6 Если вы не считаете подобную игру забавной или же вам необходимо узY нать порядковый номер какогоYнибудь ‘‘удаленного’’ столбца (например, DG), обратитесь к интерфейсу Excel. Выделите ячейку A1 и, удерживая нажатой клавишу <Shift>, нажмите несколько раз клавишу <→>. Пока длина выделенY ного диапазона ячеек не вышла за пределы экрана, порядковый номер поY следнего столбца можно узнать с помощью поля Имя (Name Box), располоY женного слева от поля ввода формулы, как показано на рис. 6.10. Рис. 6.10. Пока длина выделенного диапазона ячеек не вышла за пределы экрана, количество выделенных строк и столбцов отображается в поле Имя слева от поля ввода формулы Когда длина выделенного диапазона ячеек выйдет за пределы экрана, коY личество выделенных строк и столбцов будет отображаться с помощью всплыY вающей подсказки. В частности, порядковый номер столбца DG равен 111, как показано на рис. 6.11. Рис. 6.11. Когда длина выделенного диапазона ячеек выходит за пределы экрана, количество выделенных строк и столбцов ото& бражается с помощью всплывающей подсказки Использование ссылок в стиле R1C1 при условном форматировании ячеек При условном форматировании ячеек рекомендуется использовать R1C1Y формулы. Применение A1Yформул является нежелательным (по имеющимся наблюдениям, примерно 1 из 50 ячеек с условным форматированием, опредеY ленным с помощью A1Yформул, содержит ошибку). Задание условного форматирования с помощью пользовательского интерфейса Существует два вида условного форматирования. Открыв диалоговое окно Условное форматирование (Conditional Formatting) с помощью команды меY ню Формат Условное форматирование (Format Conditional Formatting), можно заметить, что по умолчанию в качестве критерия форматирования исY пользуется значение ячейки. Например, вы можете определить особый форY 171 172 Часть I Первые шаги мат для ячеек, содержащих отрицательные числа. Согласно второму типу усY ловного форматирования критерий форматирования определяется с помощью формулы, принимающей значение True или False. Формула может содерY жать ссылки на любые ячейки рабочего листа, а также объединять несколько критериев с помощью функций ИЛИ (OR) и И (AND). Для каждой ячейки можно определить 3 различных критерия форматирования. Рассмотрим задачу сокрытия текста ячеек, содержащих сообщения об ошибке, и выделения текста ячеек с отрицательными числами красным цвеY том. Наиболее очевидным ее решением является задание условного форматиY рования. Первый критерий будет основываться на формуле, второй YYYY на знаY чении ячейки. Пример установки подобного условного форматирования с поY мощью интерфейса Excel показан на рис. 6.12. Рис. 6.12. В качестве первого критерия форматирования ячейки ис& пользуется формула, в качестве второго — значение ячейки Задание условного форматирования с помощью VBA Задача сокрытия текста ячеек, содержащих сообщения об ошибке, может быть решена путем установки цвета заливки ячейки равным цвету ее текста. Для реализации такого решения создадим небольшой макрос VBA. Задание условного форматирования ячеек осуществляется посредством использования объекта FormatConditions. Поскольку для каждой ячейки может быть определено три критерия форматирования, следующий код сперва отменяет любое условное форматирование в пределах рабочего листа, после чего применяет два критерия форматирования к каждой непустой ячейке. Первый критерий форматирования основывается на R1C1Yформуле (тип xlExpression), второй YYYY на значении ячейки (тип xlCellValue). ОбратиY те внимание, что кроме значения ячейки необходимо указать также и операY тор сравнения. После задания критериев форматирования определяется сам формат ячеек (в данном случае YYYY цвет текста), как показано ниже: Sub ApplySpecialFormattingAll() ' Сокрытие текста ячеек, содержащих сообщения об ошибке. For Each ws In ThisWorkbook.Worksheets Стиль записи ссылок R1C1 Глава 6 ws.UsedRange.FormatConditions.Delete For Each cell In ws.UsedRange.Cells If Not IsEmpty(cell) Then ' В англоязычной версии Excel: ' cell.FormatConditions.Add Type:=xlExpression, _ ' Formula1:="=OR(ISERR(RC),ISNA(RC))" cell.FormatConditions.Add Type:=xlExpression, _ Formula1:="=ИЛИ(ЕОШИБКА(RC);ЕНД(RC))" cell.FormatConditions(1).Font.Color = _ cell.Interior.Color cell.FormatConditions.Add Type:=xlCellValue, _ Operator:=xlLess, Formula1:="0" cell.FormatConditions(2).Font.ColorIndex = 3 End If Next cell Next ws End Sub Внимание Не используйте A1&формулы для задания критерия условного форматирования в VBA. Соответствующий код может выполняться корректно лишь для небольшого числа ячеек, однако при попытке его применения к 50 и более ячейкам существует вероятность сбоя (более подробно об этом рассказывается в следующем практи& куме). Использование R1C1&формул полностью лишено подобного недостатка. Практикум Поиск минимального и максимального значения в столбце Рассмотрим классический пример использования условного форматирования ячеек. На рис. 6.13 показано, как выделить строки, содержащие минимальное и максимальное значение в определенном столбце, с помощью интерфейса Excel. Рис. 6.13. Классический пример, позволяющий выявить одну из про& блем задания условного форматирования ячеек с помощью VBA 173 174 Часть I Первые шаги Ниже приведен соответствующий код VBA: Sub FindMinMax() ' Выделить строку, содержащую наибольший доход, зеленым цветом. ' Выделить строку, содержащую наименьший доход, желтым цветом. FinalRow = Cells(Application.Rows.Count, 1).End(xlUp).Row With Range("A2:I" & FinalRow) .FormatConditions.Delete ' В англоязычной версии Excel: ' .FormatConditions.Add Type:=xlExpression, _ 'Formula1:="=RC7=MAX(C7)" .FormatConditions.Add Type:=xlExpression, _ Formula1:="=RC7=МАКС(C7)" .FormatConditions(1).Interior.ColorIndex = 4 ' В англоязычной версии Excel: ' .FormatConditions.Add Type:=xlExpression, _ 'Formula1:="=RC7=MIN(C7)" .FormatConditions.Add Type:=xlExpression, _ Formula1:="=RC7=МИН(C7)" .FormatConditions(2).Interior.ColorIndex = 6 End With End Sub Для нахождения строки с наибольшим значением в столбце G используется фор& мула МАКС(C7) (MAX(C7)), синтаксис которой позволяет считать ее и A1&, и R1C1& формулой. Вспомним, что Microsoft изначально использовала ссылки в стиле R1C1 и ввела поддержку ссылок в стиле A1 только для совместимости с Lotus 1&2&3. По& скольку в первую очередь Excel пытается проинтерпретировать формулу с исполь& зованием ссылок в стиле R1C1 (это недокументированная особенность Excel), вы& полнение приведенного выше кода приведет к желаемому результату. А теперь представьте себе, что случится, если вы зададите условное форматиро& вание с помощью формулы, ссылающейся на ячейку C7 или R22. Вы получите со& всем не тот результат, на который рассчитывали, поскольку Excel воспримет эти ссылки как ссылки на 7&й столбец и 22&ю строку, соответственно. Именно поэтому при задании условного форматирования рекомендуется использовать только R1C1&формулы. Использование ссылок в стиле R1C1 при создании формулы массива Формулы массива YYYY это одни из наиболее мощных формул в Excel. ИноY гда их называют CSEYформулами, поскольку при вводе формулы массива исY пользуется комбинация клавиш <Ctrl+Shift+Enter>. Формула массива, введенная в ячейке E20 (рис. 6.14), реализует 18 операций умножения и суммирование полученных при каждом умножении результатов. Если ввести формулу массива без применения комбинации клавиш <Ctrl+Shift+Enter>, в ячейке будет выведено сообщение об ошибке #ЗНАЧ! (#VALUE!). Обратите внимание, что при вводе формулы массива брать ее в фигурные скобки не нужно. Стиль записи ссылок R1C1 Глава 6 Рис. 6.14. Формула массива, введенная в ячейке E20, реали& зует 18 операций умножения и суммирование полученных при каждом умножении результатов. Для ввода формулы необхо& димо использовать комбинацию клавиш <Ctrl+Shift+Enter> Ниже приведен код, использующийся для ввода формулы массива: Sub EnterArrayFormulas() ' Ввод формулы массива. FinalRow = Cells(65536, 1).End(xlUp).Row Cells(FinalRow + 1, 5).FormulaArray = _ "=SUM(R2C[-1]:R[-1]C[-1]*R2C:R[-1]C)" End Sub Несмотря на то что в интерфейсе Excel отображаются ссылки в стиле A1, помните, что формулу массива необходимо вводить с использованием ссылок в формате R1C1. Следующий шаг Согласно стилю R1C1, обращение к ячейке осуществляется по номеру ее строки (R) и столбца (С). Использование R1C1Yформул позволяет создавать более эффективный и корректный код VBA. Кроме того, употребление адреY сации R1C1 является обязательным требованием при создании формул массиY вов и при задании условного форматирования ячеек. В следующей главе будут рассмотрены именованные диапазоны ячеек YYYY еще одно средство, упроY щающее разработку макросов в Excel. 175 Глава 7 Èìåíà Поле для ввода имени диапазона ячеек, формулы, массива и т.п. распоY лагается на рабочем листе слева от поY ля ввода формулы. Присваивание имен различным объектам Excel сущеY ственно упрощает их использование. Возможность создавать имена и манипулировать ими доступна также и в VBA. Далее в этой главе будут расY смотрены различные типы имен и способы их применения на практике. Глобальные и локальные имена Все имена можно разделить на две категории YYYY глобальные имена (область действия распространяется на всю раY бочую книгу) и локальные имена (область действия ограничена одним рабочим листом). В рабочей книге моY жет содержаться несколько одинакоY вых локальных имен, однако только одно уникальное глобальное имя. При обращении к локальному имени необY ходимо указывать соответствующий рабочий лист (Лист1!Фрукты), тогда как при обращении к глобальному имени никаких дополнительных треY бований не ставится (Фрукты). Показанное на рис. 7.1 диалогоY вое окно Присвоение имени (Define Name) содержит смешанный спиY сок, состоящий из глобальных и лоY кальных имен. Имя рабочего листа, соответстY вующего локальному имени, вывоY дится справа от последнего. 7 Глобальные и локальные имена .............................................. 177 Создание имен.............................. 179 Удаление имен .............................180 Типы имен......................................180 Скрытие имен ............................... 185 Проверка существования имени.............................................. 185 Следующий шаг............................ 187 178 Часть I Первые шаги Рис. 7.1. Диалоговое окно Присвоение имени со& держит список глобальных и локальных имен При совпадении глобального и локального имени в окне Присвоение имени выводится только локальное имя при условии, что соответствующий ему рабочий лист является активным (рис. 7.2). Рис. 7.2. При совпадении глобального и локаль& ного имени в списке имен выводится только ло& кальное имя при условии, что соответствующий ему рабочий лист является активным Если же активным является другой рабочий лист, в окне Присвоение имени выводится только глобальное имя, как показано на рис. 7.3. Совет Средство проверки вводимых значений (чтобы воспользоваться этим средством, выберите команду меню Excel Данные Проверка (Data Validation)) позволяет выделить диапазон ячеек только на активном рабочем листе. Чтобы обойти это ограничение, присвойте имя диапазону данных, которые вы хотите проверить. Имена Глава 7 179 Рис. 7.3. При совпадении глобального и локаль& ного имени в списке имен выводится только гло& бальное имя при условии, что соответствующий локальному имени рабочий лист НЕ является активным Создание имен Рассмотрим пример кода, сгенерированного средством записи макросов при создании именованного диапазона ячеек: ActiveWorkbook.Names.Add Name:="Фрукты", _ RefersToR1C1:="=Лист2!R1C1:R6C6" Как следует из приведенного выше кода, именованный диапазон ячеек ноY сит имя Фрукты и охватывает ячейки A1:F6 (R1C1:R6C6). Формулу, опредеY ляющую диапазон ячеек, следует предварить знаком равенства и взять в каY вычки. Адрес диапазона задается с помощью абсолютной ссылки (с использоY ванием знаков $) или с помощью ссылки в стиле R1C1. Если диапазон ячеек находится на текущем активном листе, имя последнего можно не указывать (тем не менее, указание полного адреса диапазона ячеек повышает читабельY ность кода). Чтобы создать локальное имя, предварите его именем рабочего листа, как показано ниже: ActiveWorkbook.Names.Add Name:="Лист2!Фрукты", _ RefersToR1C1:="=Лист2!R1C1:R6C6" или воспользуйтесь коллекцией Names данного рабочего листа: Worksheets("Лист1").Names.Add Name:="Фрукты", _ RefersToR1C1:="=Лист1!R1C1:R6C6" Существует и более простой способ создания имен. Ниже приведены приY меры создания глобального и локального имени, соответственно: Range("A1:F6").Name = "Фрукты" Range("A1:F6").Name = "Лист1!Фрукты" К сожалению, указанный выше способ имеет одно существенное ограниY чение YYYY он позволяет создавать только имена диапазонов ячеек. Для создания имен формул, строк, чисел и массивов необходимо использовать метод Add. 180 Часть I Первые шаги Чтобы изменить существующее имя, воспользуйтесь свойством Name, как показано ниже: Names("Фрукты").Name = "Товар" В результате выполнения приведенного выше кода имя Фрукты становится недействительным; к соответствующему диапазону ячеек можно обратиться по новому имени Товар. Если строка Range("A1:F6").Name = "Фрукты" расположена выше строки Range("A1:F6").Name = "Товар", имя Товар переопределяет имя Фрукты, как показано на рис. 7.4. Рис. 7.4. Локальное имя Фрукты было заменено локальным именем Товар. Обратите внимание на то, что глобальное имя Фрукты по&прежнему доступно Попытка обращения к локальному имени Фрукты приведет к возникновеY нию ошибки, поскольку такое имя больше не существует. Удаление имен Чтобы удалить имя, воспользуйтесь методом Delete, как показано ниже: Names("ТоварНомер").Delete Попытка удаления несуществующего имени приведет к возникновению ошибки. Внимание При совпадении глобального и локального имени следует обратить особое вни& мание на необходимость корректного указания удаляемого имени. Типы имен Наиболее распространенный способ использования имен заключается в создании именованных диапазонов ячеек. Тем не менее, предназначение имен гораздо шире YYYY они позволяют упростить доступ к большим объемам Имена Глава 7 информации практически любого рода. В отличие от переменных, имена храY нят данные и после завершения выполнения программного кода. В следующих разделах рассматриваются имена формул, строк, чисел и массивов. Имена формул Синтаксис создания имени формулы аналогичен синтаксису создания имени диапазона ячеек, поскольку при определении последнего используется формула: Names.Add Name:="ТоварСписок", RefersTo:="=СМЕЩ(_ Лист2!A2;0;0;СЧЁТЗ(Лист2!$A:$A))" (В англоязычной версии Excel функции СМЕЩ соответствует функция OFFSET.) В результате выполнения приведенной выше строки кода будет определено имя формулы, ссылающейся на динамический столбец (рис. 7.5). Рис. 7.5. Пример создания имени формулы Имена строк При создании имени строки значение последней заключается в кавычки (знак равенства, как при создании имени диапазона и формулы, не ставится): Names.Add Name:="Компания", RefersTo:="КомпанияА" На рис. 7.6 показано диалоговое окно Присвоение имени (Define Name), в списке имен которого содержится имя строки. Совет Поскольку имена не теряют свои значения между сеансами работы в Excel, они больше подходят для хранения данных, чем ячейки. Рассмотрим задачу хранения названия компании, ставшей лучшим поставщиком за определенный промежуток времени. Одним из решений этой задачи является создание имени ЛучшийПоставщик. Альтернативный способ хранения имени лучшего поставщика заключа& ется в использовании ячейки на отдельном рабочем листе, что менее удобно. 181 182 Часть I Первые шаги Рис. 7.6. Пример создания имени строки Следующая процедура демонстрирует пример использования ячеек для хранения данных между сеансами работы в Excel: Sub NoNames(ByRef CurrentTop As String) TopSeller = Worksheets("Переменные").Range("A1").Value If CurrentTop = TopSeller Then MsgBox ("Лучшим поставщиком снова стал ' &&TopSeller &'!") Else MsgBox ("Лучшим поставщиком стал " & CurrentTop) End If End Sub Ниже приведен код аналогичной процедуры, использующий для хранения данных между сеансами работы в Excel имена: Sub WithNames() If Evaluate("Текущий") = Evaluate("Предыдущий") Then MsgBox ("Лучшим поставщиком снова стал " & _ Evaluate("Предыдущий") & "!") Else MsgBox ("Лучшим поставщиком стал " & Evaluate("Текущий")) End If End Sub Поскольку Текущий и Предыдущий являются заранее созданными именаY ми, доступ к их значениям осуществляется напрямую, без необходимости созY дания переменных. Обратите внимание, что для доступа к значению имени используется метод Evaluate. Внимание Длина строки не может превышать 255 символов. Имена чисел Имена могут использоваться и для хранения чисел. Ниже приведены два примера создания имени числа YYYY с участием переменной и без нее: NumofSales = 5123 Names.Add Name:="ПродажиВсего", RefersTo:=NumofSales Names.Add Name:="ПродажиВсего", RefersTo:=5123 Имена Глава 7 183 Обратите внимание на отсутствие кавычек и знака равенства. Кавычки способны превратить число в строку, а знак равенства YYYY в формулу. Чтобы извлечь значение имени числа, можно воспользоваться двумя метоY дами YYYY простым и сокращенным: NumofSales = Names("ПродажиВсего").Value NumofSales = [ПродажиВсего] Следует отметить, что использование квадратных кавычек равносильно вызову метода Evaluate и способно в определенной степени затруднить поY нимание программного кода. Чтобы сделать код более читабельным, рекоY мендуется воздержаться от использования метода Evaluate или снабдить соY ответствующие фрагменты кода комментариями. Имена массивов Имена могут использоваться для хранения целых массивов. Размер массиY ва ограничен 256 столбцами и 5461 элементами (более подробно массивы расY сматриваются в главе 17, ‘‘Массивы’’). Создание имени массива похоже на создание имени числа: Sub NamedArray() Dim myArray(10, 5) Dim i As Integer, j As Integer ' Заполнение массива myArray значениями. For i = 1 To 10 For j = 1 To 5 myArray(i, j) = i + j Next j Next i ' Создание имени массива. Names.Add Name:="Массив", RefersTo:=myArray End Sub Как и при создании имени числа, обратите внимание на отсутствие кавыY чек и знака равенства. Зарезервированные имена Excel содержит несколько зарезервированных имен, которые не рекоменY дуется использовать при создании собственных имен. Рассмотрим небольшой пример. Выделите произвольный диапазон ячеек на рабочем листе и выберите команду меню Excel Файл Область печати Задать (File Print Area Set Print Area). Как показано на рис. 7.7, Excel автоматически присваивает выделенному диапазону ячеек имя Область_печати (Print_Area). Это одно из зарезервированных имен, значение которого сохраняется межY ду сеансами работы в Excel (попробуйте сохранить, закрыть, а затем снова отY крыть рабочую книгу, и вы убедитесь, что имя Область_печати соответствуY ет все тому же диапазону ячеек). 184 Часть I Первые шаги Рис. 7.7. Область_печати — одно из заре& зервированных имен Excel Внимание Каждый рабочий лист имеет собственную область печати. Назначение новой об& ласти печати переопределяет текущее значение имени Область_печати для дан& ного рабочего листа. Ниже приведен список зарезервированных имен Excel: Критерии (Criteria); База_данных (Database); Извлечь (Extract); Область_печати (Print_Area); Заголовки_для_печати (Print_Titles). Имена Критерии и Извлечь используются при копировании результата применения расширенного фильтра в новое место (чтобы применить расY ширенный фильтр, выберите команду меню Excel Данные Фильтр Расширенный фильтр (Data Filter Advanced Filter)). Имя База_данных использовалось в предыдущих версиях Excel для обоY значения данных, которыми манипулировали определенные функции. Сейчас имя База_данных используется разве что при создании форм ввода данных (чтобы создать форму ввода данных, выберите команду меню Excel Данные Форма (Data Form)). Имя Область_печати используется при задании области печати (выберите команду Файл Область печати Задать) или ее изменении посредством диалогового окна Параметры страницы (Page Setup) (выберите команду Файл Параметры страницы (File Page Setup)). Имя Заголовки_для_печати используется при установке заголовков пеY чати (выберите команду Файл Параметры страницы, перейдите во вкладку Лист (Sheet) и установите флажок Заголовки строк и столбцов (Row and coY lumn headings)). Зарезервированные имена (а также похожие на них) не рекомендуется исY пользовать при создании собственных имен. Предположим, что вы создали имя ОбластьПечати и по ошибке написали следующий код: Worksheets("Лист4").Names("Область_печати").Delete Имена Глава 7 185 В результате выполнения приведенного выше кода вместо имени ОбластьПечати будет удалено зарезервированное имя Excel. Скрытие имен Благодаря наличию у объекта Name свойства Visible имя можно скрыть. Чтобы скрыть имя, установите значение свойства Visible равным False; чтобы вновь сделать имя видимым, установите значение свойства Visible равным True: Names.Add Name:="ТоварНомер", RefersTo:="=$A$1", Visible:=False Внимание Если пользователь создаст имя, совпадающее с существующим скрытым именем, последнее будет переопределено безо всякого предупреждения. Чтобы избежать этого, защитите рабочий лист. Проверка существования имени Следующая функция проверяет существование имени (включая скрытые имена), за исключением зарезервированных имен Excel: Function NameExists(FindName As String) As Boolean Dim Rng As Range Dim myName As String On Error Resume Next myName = ActiveWorkbook.Names(FindName).Name If Err.Number = 0 Then NameExists = True Else NameExists = False End Function Предыдущий код также является великолепным примером того, как можно извлечь выгоду из ошибок. Если переданное функции имя не существует, генеY рируется ошибка, однако благодаря строке On Error Resume Next код проY должает выполняться как ни в чем не бывало. Определить факт наличия ошибки поможет свойство Err.Number. Если значение этого свойства равно 0, переY данное функции имя существует (при выполнении кода не было сгенерировано ошибки), в противном случае YYYY нет (была сгенерирована ошибка). Практикум Использование именованных диапазонов ячеек при поиске значений в столбце данных Рассмотрим следующую задачу. Каждый день в Excel импортируется файл со све& дениями о продажах товара в сети розничных магазинов. Файл содержит номера 186 Часть I Первые шаги магазинов, но не их названия. Необходимо реализовать автоматическое добавле& ние названий магазинов с целью последующего создания отчетов. Типичное решение этой задачи заключается в создании таблицы с названиями всех магазинов на дополнительном рабочем листе и применении макроса для до& бавления названий магазинов на основном рабочем листе. Всю процедуру можно разбить на 6 этапов. 1. Импортирование файла данных. 2. Нахождение всех уникальных номеров магазинов. 3. Проверка, все ли номера магазинов внесены в таблицу соответствия номеров и названий магазинов. 4. Если необходимо, занести в таблицу соответствия номеров и названий магази& нов сведения о новых магазинах. 5. Переопределить именованный диапазон ячеек так, чтобы он включал в себя обновленную таблицу соответствия номеров и названий магазинов. 6. Добавить названия магазинов на основном рабочем листе. Представленный далее исходный код макроса реализует приведенную выше по& следовательность действий: Sub ImportData() Dim WSD As Worksheet Dim WSM As Worksheet Dim WB As Workbook Set WB = ThisWorkbook ' Основной рабочий лист - "Данные". Set WSD = ThisWorkbook.Worksheets("Данные") ' Вспомогательный рабочий лист - "Таблица". Set WSM = ThisWorkbook.Worksheets("Таблица") ' Код импорта файла закомментирован. ' Предполагается, что данные уже импортированы. ' Импортировать файл. ' Workbooks.Open Filename:="C:\Sales.csv" ' Скопировать данные и закрыть файл. 'Range("A1").CurrentRegion.Copy Destination:=WSD.Range("A1") 'ActiveWorkbook.Close SaveChanges:=False ' Активизировать рабочий лист с данными. ' Составить список уникальных номеров магазинов. WSD.Activate FinalRow = Cells(65536, 1).End(xlUp).Row Range("A1").Resize(FinalRow, 1).AdvancedFilter _ Action:=xlFilterCopy, CopyToRange:=Range("Z1"), Unique:=True ' Проверить, все ли номера магазинов находятся в таблице ' соответствия номеров и названий магазинов. FinalStore = Range("Z65536").End(xlUp).Row Range("AA1").Value = "В списке?" ' В англоязычной версии Excel: ' Range("AA2:AA" & FinalStore).FormulaR1C1 = _ ' "=ISNA(VLOOKUP(RC[-1],СписокМагазинов,1,False))" Имена Глава 7 187 Range("AA2:AA" & FinalStore).FormulaR1C1Local = _ "=ЕНД(ВПР(RC[-1];СписокМагазинов;1;ЛОЖЬ))" ' Найти строку для ввода сведений о новом магазине. NextRow = WSM.Range("A65536").End(xlUp).Row + 1 ' Добавить сведения о новых магазинах. For i = 2 To FinalStore If Cells(i, 27).Value = True Then ThisStore = Cells(i, 26).Value WSM.Cells(NextRow, 1).Value = ThisStore WSM.Cells(NextRow, 2).Value = InputBox(Prompt:=" _ Введите имя магазина " & ThisStore, Title:="Найден новый магазин") NextRow = NextRow + 1 End If Next i ' Удалить вспомогательный список магазинов. Range("Z1:AA" & FinalStore).Clear ' Переопределить имя "СписокМагазинов". FinalStore = WSM.Range("A65536").End(xlUp).Row WSM.Range("A1:B" & FinalStore).Name = "СписокМагазинов" ' Добавить названия магазинов. Range("B1").EntireColumn.Insert Range("B1").Value = "МагазинНазвание" ' В англоязычной версии Excel: ' Range("B2:B" & FinalRow).FormulaR1C1 = _ "=VLOOKUP(RC1,СписокМагазинов,2,False)" Range("B2:B" & FinalRow).FormulaR1C1Local = _ "=ВПР(RC1;СписокМагазинов;2;ЛОЖЬ)" 'Заменить формулы значениями. Range("B2:B" & FinalRow).Value = Range("B2:B" & FinalRow).Value End Sub Следующий шаг Следующая глава посвящена событиям YYYY концепции, позволяющей VBA реагировать на действия пользователя, такие как выделение ячейки, смена теY кущего активного рабочего листа и т.п. Глава 8 Ñîáûòèÿ События позволяют реагировать на всевозможные действия, происхоY дящие в пределах текущего сеанса работы с Excel. Различают следующие уровни соY бытий. Уровень приложения. ОхватыY вает действия, имеющие неY посредственное отношение к Excel, например Application_NewWorkbook. Уровень рабочей книги. ОхваY тывает действия, имеющие непосредственное отношение к рабочей книге, например Workbook_Open. Уровень рабочего листа. ОхватыY вает действия, имеющие непоY средственное отношение к раY бочему листу, например Worksheet_SelectionChange. Уровень листа диаграммы. ОхY ватывает действия, имеющие непосредственное отношение к листу диаграммы, например Chart_Activate. Код обработки событий рабочей книги размещается в модуле ЭтаКнига (ThisWorkbook), код обработY ки событий рабочего листа YYYY в модуY ле этого рабочего листа (например, Лист1), код обработки событий листа диаграммы и Excel — в соответствуюY щих модулях классов. При обработке событий можно вызывать процедуры и функции из других модулей. 8 Использование событий............. 190 События рабочей книги...............191 События рабочего листа............. 199 События листа диаграммы....... 204 События приложения................. 208 Следующий шаг............................ 214 190 Часть I Первые шаги Таким образом, нет необходимости дублировать один и тот же код для неY скольких рабочих листов YYYY его можно поместить в отдельный модуль и вызыY вать при обработке события каждого листа. В этой главе рассматриваются события различных уровней и их использоY вание на практике. Использование событий Запомнить синтаксис всех событий достаточно сложно. К счастью, редакY тор Visual Basic позволяет быстро выбрать требуемое событие и добавить код его обработки в соответствующий модуль. При выборе модуля ЭтаКнига (ThisWorkbook), модуля листа или модуля класса соответствующие события становятся доступны посредством расY крывающихся списков Object (Объект) и Procedure (Процедура), как поY казано на рис. 8.1. Рис. 8.1. Редактор Visual Basic позволяет выбрать требуемое событие с помощью раскрываю& щихся списков Object и Procedure После выбора объекта из раскрывающегося списка Object в раскрывающемY ся списке Procedure появляются соответствующие этому объекту события. ВыY бор события приводит к автоматической генерации верхнего (Private Sub) и нижнего (End Sub) заголовка процедуры, как показано на рис. 8.2. Рис. 8.2. Редактор Visual Basic автоматически генерирует верхний и нижний заголовок процедуры Параметры событий Некоторые события имеют параметры (например, Target и Cancel), предназначенные для передачи коду обработки события определенной инY формации. Рассмотрим событие Worksheet_BeforeRightClick, срабатыY вающее перед выполнением действия, соответствующего щелчку правой кнопкой мыши в пользовательском интерфейсе Excel. Присвоение параметру События Глава 8 Cancel значения True предотвращает выполнение стандартного действия, в данном случае YYYY отображения контекстного меню: Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, _ Cancel As Boolean) Cancel = True End Sub Запрет обработки событий Некоторые события могут вызывать другие события, включая самих себя. Например, событие Worksheet_Change срабатывает при изменении ячейки на рабочем листе. Если при обработке этого события вновь будет изменена ячейка, событие сработает снова, что приведет к зацикливанию. Чтобы избежать описанной выше ситуации, запретите обработку событий в начале кода процедуры и разрешите ее в конце, как показано ниже: Private Sub Worksheet_Change(ByVal Target As Range) Application.EnableEvents = False Range("A1").Value = Target.Value Application.EnableEvents = True End Sub На заметку Чтобы прервать выполнение макроса, нажмите клавишу <Esc> или воспользуй& тесь комбинацией клавиш <Ctrl+Break>. События рабочей книги Ниже приведен список событий, существующих на уровне рабочей книги Excel: Activate; Deactivate; Open; BeforeSave; BeforePrint; BeforeClose; NewSheet; SheetActivate; SheetDeactivate; SheetCalculate; SheetBeforeDoubleClick; SheetSelectionChange; 191 192 Часть I Первые шаги SheetBeforeRightClick; SheetChange; WindowResize; WindowActivate; WindowDeactivate; AddinInstall; AddinUninstall. Событие Workbook_Activate() Событие Workbook_Activate срабатывает, когда содержащая это собыY тие рабочая книга становится активной. Событие Workbook_Deactivate() Событие Workbook_Deactivate срабатывает, когда содержащая это соY бытие рабочая книга перестает быть активной. Событие Workbook_Open() Событие Workbook_Open является стандартным событием рабочей книги. Код его обработки выполняется непосредственно при открытии файла рабочей книги, перед выводом на экран пользовательского интерфейса. Одним из наиY более распространенных применений события Workbook_Open является проY верка имени пользователя и определение его прав на доступ к рабочей книге. Следующий код проверяет имя пользователя и, если оно не равно ‘‘Администратор’’, защищает рабочий лист от внесения изменений со стороY ны пользователя (за это отвечает параметр UserInterfaceOnly): Private Sub Workbook_Open() Dim sht As Worksheet If Application.UserName <> "Администратор" Then For Each sht In Worksheets sht.Protect UserInterfaceOnly:=True Next sht End If End Sub Событие Workbook_Open можно применять и для создания пользовательY ских меню или панелей управления. Следующий код создает меню Меню MrExcel с двумя пунктами, как показано на рис. 8.3. См. также Более подробно создание пользовательского меню рассматривается в разделе “Создание пользовательского меню” главы 24 на с. 575. События Private Dim Dim Dim Dim Глава 8 193 Sub Workbook_Open() cbWSMenuBar As CommandBar Ctrl As CommandBarControl, muCustom As CommandBarControl iHelpIndex As Integer sht As Worksheet ' Инициализация событий приложения и листа диаграммы. InitializeAppEvent InitializeChart CreateToolbar Set cbWSMenuBar = Application.CommandBars("Worksheet menu bar") iHelpIndex = cbWSMenuBar.Controls("Справка").Index Set muCustom = cbWSMenuBar.Controls.Add(Type:=msoControlPopup, _ Before:=iHelpIndex, Temporary:=True) For Each Ctrl In cbWSMenuBar.Controls If Ctrl.Caption = "Меню &MrExcel" Then cbWSMenuBar.Controls("Меню MrExcel").Delete End If Next Ctrl With muCustom .Caption = "Меню &MrExcel" With .Controls.Add(Type:=msoControlButton) .Caption = "&Импорт" .OnAction = "Import" End With With .Controls.Add(Type:=msoControlButton) .Caption = "&Экспорт" .OnAction = "Export" End With End With End Sub Рис. 8.3. Событие Workbook_Open можно применять для создания пользова& тельского меню Событие Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) Событие Workbook_BeforeSave срабатывает при попытке сохранения рабочей книги. Чтобы отобразить диалоговое окно Сохранение документа (Save As), установите значение параметра SaveAsUI равным True. Чтобы заY претить сохранение рабочей книги, установите равным True значение параY метра Cancel. Событие Workbook_BeforePrint(Cancel As Boolean) Событие Workbook_BeforePrint срабатывает при попытке печати раY бочей книги (способ инициирования процесса печати YYYY с помощью команды меню, кнопки панели инструментов, комбинации клавиш или программного 194 Часть I Первые шаги кода YYYY не играет роли). Чтобы запретить печать, установите значение параY метра Cancel равным True. Приведенный ниже код отслеживает попытки печати рабочего листа, заноY ся в журнал дату и время печати, имя пользователя и имя рабочего листа (рис. 8.4): Private Sub Workbook_BeforePrint(Cancel As Boolean) Dim LastRow As Long Dim PrintLog As Worksheet Set PrintLog = Worksheets("PrintLog") LastRow = PrintLog.Range("A65536").End(xlUp).Row + 1 With PrintLog .Cells(LastRow, 1).Value = Now() .Cells(LastRow, 2).Value = Application.UserName .Cells(LastRow, 3).Value = ActiveSheet.Name End With End Sub Рис. 8.4. Событие Workbook_BeforePrint можно ис& пользовать для ведения журнала печати Событие Workbook_BeforePrint можно использовать для создания верхнего и нижнего колонтитула печатаемой страницы. Несмотря на то, что теперь это можно сделать с помощью диалогового окна Параметры страницы (Page Setup), раньше подобная возможность реализовывалась только с помоY щью программного кода: Private Sub Workbook_BeforePrint(Cancel As Boolean) ActiveSheet.PageSetup.RightFooter = ActiveWorkbook.FullName End Sub Событие Workbook_BeforeClose(Cancel As Boolean) Событие Workbook_BeforeClose срабатывает при попытке закрыть раY бочую книгу. Чтобы сделать закрытие рабочей книги невозможным, устаноY вите значение параметра Cancel равным True. Событие Workbook_BeforeClose можно применить для удаления польY зовательского меню: Private Sub Workbook_BeforeClose(Cancel As Boolean) Dim cbWSMenuBar As CommandBar On Error Resume Next Set cbWSMenuBar = Application.CommandBars("Worksheet menu bar") cbWSMenuBar.Controls("Меню MrExcel").Delete End Sub Приведенный выше код имеет один недостаток. Если в рабочую книгу быY ли внесены изменения, Excel предложит сохранить их, отобразив соответстY События Глава 8 195 вующее диалоговое окно после выполнения кода обработки события Workbook_BeforeClose. Если вы передумаете закрывать книгу и щелкните на кнопке Отмена (Cancel), то уже не сможете вернуть удаленное меню. Решение этой проблемы заключается в выводе на экран собственного диаY логового окна сохранения рабочей книги: Private Dim Dim Dim Sub Workbook_BeforeClose(Cancel As Boolean) Msg As String Response cbWSMenuBar As CommandBar If Not ThisWorkbook.Saved Then Msg = "Сохранить изменения, внесенные в " & Me.Name & "?" Response = MsgBox(Msg, vbQuestion + vbYesNoCancel) Select Case Response Case vbYes ThisWorkbook.Save Case vbNo ThisWorkbook.Saved = True Case vbCancel Cancel = True Exit Sub End Select End If Set myAppEvent = Nothing Set myClassModule = Nothing On Error Resume Next Set cbWSMenuBar = Application.CommandBars("Worksheet menu bar") cbWSMenuBar.Controls("Меню MrExcel").Delete End Sub Событие Workbook_NewSheet(ByVal Sh As Object) Событие Workbook_NewSheet срабатывает при добавлении в текущую активную рабочую книгу нового листа. Sh — это объект нового рабочего листа или листа диаграммы. Событие Workbook_WindowResize(ByVal Wn As Window) Событие Workbook_WindowResize срабатывает при изменении размера окна текущей активной рабочей книги. Wn — это объект окна. На заметку Событие Workbook_WindowResize срабатывает только при изменении размера окна текущей активной рабочей книги. Изменение размера окна Excel является со& бытием уровня приложения и не имеет никакого отношения к событиям уровня рабочей книги. 196 Часть I Первые шаги Следующий код делает невозможным изменение размера окна текущей акY тивной рабочей книги: Private Sub Workbook_WindowResize(ByVal Wn As Window) Wn.EnableResize = False End Sub Внимание Запрет изменения размера окна рабочей книги приводит к удалению кнопок окна Свернуть (Minimize) и Развернуть (Maximize). Чтобы вернуть кнопки на место, выполните в окне Immediate (Быстрое выполнение) строку ActiveWindow.EnableResize = True. Событие Workbook_WindowActivate(ByVal Wn As Window) Событие Workbook_WindowActivate срабатывает при активизации окY на любой рабочей книги. Wn — это объект окна. На заметку Событие Workbook_WindowActivate срабатывает только при активизации окна рабочей книги. Событие Workbook_WindowDeactivate(ByVal Wn As Window) Событие Workbook_WindowDeactivate срабатывает при деактивизации окна любой рабочей книги. Wn — это объект окна. На заметку Событие Workbook_WindowDeactivate срабатывает только при деактивизации окна рабочей книги. Событие Workbook_AddinInstall() Событие Workbook_AddinInstall срабатывает при установке рабочей книги в качестве надстройки (чтобы установить надстройку, выберите команY ду меню Excel Сервис Надстройки (Tools AddYIns)). Обратите внимание, что событие Workbook_AddinInstall не срабатывает при двойном щелчке на значке файла надстройки (файл с расширением .xla). Событие Workbook_AddinUninstall() Событие Workbook_AddinUninstall срабатывает при удалении рабочей книги, используемой в качестве надстройки. Обратите внимание, что надY стройка не закрывается автоматически. События Глава 8 197 Событие Workbook_SheetActivate(ByVal Sh As Object) Событие Workbook_SheetActivate срабатывает при активизации люY бого рабочего листа или листа диаграммы в рабочей книге. Sh — это объект активного листа. Кроме того, активизация рабочего листа или листа диаграммы приводит к срабатыванию событий активного рабочего листа (Worksheet_Activate) или листа диаграммы (Chart_Activate). Событие Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие Workbook_SheetBeforeDoubleClick срабатывает при выY полнении двойного щелчка на рабочем листе или листе диаграммы текущей активной рабочей книги. Sh — это объект активного рабочего листа или листа диаграммы, а Target — объект, на котором был выполнен двойной щелчок. Чтобы запретить стандартное действие, предпринимаемое при обработке двойного щелчка, установите значение параметра Cancel равным True. Кроме того, двойной щелчок на рабочем листе или листе диаграммы приводит к срабатыванию событий активного рабочего листа (Worksheet_BeforeDoubleClick) или листа диаграммы (Chart_BeforeDoubleClick). Событие Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие Workbook_SheetBeforeRightClick срабатывает при выполY нении щелчка правой кнопкой мыши на рабочем листе или листе диаграммы текущей активной рабочей книги. Sh — это объект активного рабочего листа или листа диаграммы, а Target — объект, на котором был выполнен щелчок правой кнопкой мыши. Чтобы запретить стандартное действие, предприниY маемое при обработке щелчка правой кнопкой мыши, установите значение параметра Cancel равным True. Кроме того, щелчок правой кнопкой мыши на рабочем листе или листе диаграммы приводит к срабатыванию событий активного рабочего листа (Worksheet_BeforeRightClick) или листа диаграммы (Chart_BeforeRightClick). Событие Workbook_SheetCalculate(ByVal Sh As Object) Событие Workbook_SheetCalculate срабатывает при пересчете рабоY чего листа или обновлении листа диаграммы. Sh — это объект активного раY бочего листа или листа диаграммы. 198 Часть I Первые шаги Кроме того, пересчет рабочего листа или обновление листа диаграммы приводит к срабатыванию событий активного рабочего листа (Worksheet_ Calculate) или листа диаграммы (Chart_Calculate). Событие Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range) Событие Workbook_SheetChange срабатывает при изменении любого диапазона ячеек на рабочем листе. Sh — это объект рабочего листа, Target — объект измененного диапазона ячеек. Кроме того, изменение диапазона ячеек рабочего листа приводит к срабаY тыванию события конкретного рабочего листа (Worksheet_Change). Событие Workbook_SheetDeactivate(ByVal Sh As Object) Событие Workbook_SheetDeactivate срабатывает при деактивизации любого рабочего листа или листа диаграммы в текущей активной рабочей книге. Sh — это объект рабочего листа или листа диаграммы, который был деY активизирован. Кроме того, деактивизация рабочего листа или листа диаграммы приводит к срабатыванию событий конкретного рабочего листа (Worksheet_Deactivate) или листа диаграммы (Chart_Deactivate). Событие Workbook_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As Hyperlink) Событие Workbook_SheetFollowHyperlink срабатывает при щелчке на гиперссылке, расположенной на любом рабочем листе активной рабочей книги. Sh — это объект активного рабочего листа, Target — объект гиперY ссылки. Кроме того, щелчок на гиперссылке, расположенной на рабочем листе, приводит к срабатыванию события активного рабочего листа (Worksheet_ FollowHyperlink). Событие Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) Событие Workbook_SheetSelectionChange срабатывает при выделеY нии нового диапазона ячеек на любом рабочем листе активной рабочей книги. Sh — это объект активного рабочего листа, Target — объект выделенного диапазона ячеек. Кроме того, выделение нового диапазона ячеек приводит к срабатыванию события активного рабочего листа (Worksheet_SelectionChange). События Глава 8 199 События рабочего листа Ниже приведен список событий, существующих на уровне рабочего листа Excel: Activate; BeforeDoubleClick; BeforeRightClick; Calculate; Change; Deactivate; FollowHyperlink; SelectionChange. Событие Worksheet_Activate() Событие Worksheet_Activate срабатывает при активизации соответстY вующего этому событию рабочего листа. Примером использования события Worksheet_Activate является создание плавающей панели инструментов: Private Sub Worksheet_Activate() On Error Resume Next Application.CommandBars("Панель инструментов _ MrExcel").Visible = True End Sub Событие Worksheet_Deactivate() Событие Worksheet_Deactivate срабатывает при деактивизации соотY ветствующего этому событию рабочего листа: Private Sub Worksheet_Deactivate() On Error Resume Next Application.CommandBars("Панель инструментов _ MrExcel").Visible = False End Sub На заметку При переключении между рабочими листами первым срабатывает событие Worksheet_Deactivate исходного рабочего листа, и только затем — событие Worksheet_Activate активизированного рабочего листа. Событие Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean) Событие Worksheet_BeforeDoubleClick срабатывает в результате двойного щелчка на соответствующем этому событию рабочем листе. Tar- 200 Часть I Первые шаги get — это объект выделенного диапазона ячеек. По умолчанию параметр Cancel имеет значение False. Присвоение этому параметру значения True приведет к отмене стандартного действия, предпринимаемого при обработке двойного щелчка (в данном случае YYYY переход в режим изменения содержиY мого ячейки). Следующий код делает невозможным переход в режим изменения содерY жимого ячейки путем двойного щелчка на ней: Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, _ Cancel As Boolean) Cancel = True End Sub На заметку Приведенный выше код не запрещает применять двойной щелчок для изменения высоты строки и ширины столбца. Запретив использование двойного щелчка для перехода в режим изменеY ния содержимого ячейки, ему можно найти другое применение. Выполнение следующего кода приведет к смене цвета заливки ячейки: Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, _ Cancel As Boolean) Target.Interior.ColorIndex = 3 End Sub Событие Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean) Событие Worksheet_BeforeRightClick срабатывает при щелчке праY вой кнопкой мыши на рабочем листе. Target — это объект, на котором щелкнули правой кнопкой мыши. Установка значения параметра Cancel равным True запрещает выполнение стандартного действия, предпринимаеY мого при обработке щелчка правой кнопкой мыши. Событие Worksheet_Calculate() Событие Worksheet_Calculate срабатывает при пересчете рабочего листа. Рассмотрим небольшой пример. Если прибыль, полученная за определенY ный месяц текущего года, превысила прибыль, полученную за аналогичный период в прошлом году, под названием месяца выводится зеленая стрелка, указывающая вверх; в противном случае выводится красная стрелка, указыY вающая вниз, как показано на рис. 8.5: Private Sub Worksheet_Calculate() Select Case Range("C3").Value Case Is > Range("C4").Value SetArrow 10, msoShapeDownArrow Case Is < Range("C4").Value События Глава 8 201 SetArrow 3, msoShapeUpArrow End Select End Sub Private Sub SetArrow(ByVal ArrowColor As Integer, ByVal ArrowDegree) ' Удалить все существующие фигуры. For Each Sh In ActiveSheet.Shapes If Left(Sh.Name, 4) = "Auto" Then Sh.Delete End If Next Sh ActiveSheet.Shapes.AddShape(ArrowDegree, 17.25, _ 43.5, 5, 10).Select With Selection.ShapeRange With .Fill .Visible = msoTrue .Solid .ForeColor.SchemeColor = ArrowColor .Transparency = 0# End With With .Line .Weight = 0.75 .DashStyle = msoLineSolid .Style = msoLineSingle .Transparency = 0# .Visible = msoTrue .ForeColor.SchemeColor = 64 .BackColor.RGB = RGB(255, 255, 255) End With End With Range("A3").Select End Sub Рис. 8.5. Пример использования события Worksheet_Calculate для добавления на рабочий лист фигуры 202 Часть I Первые шаги Событие Worksheet_Change(ByVal Target As Range) Событие Worksheet_Change срабатывает при изменении содержимого ячейки, например, при вводе, редактировании или удалении текста. Target — это объект ячейки, содержимое которой было изменено. На заметку Событие Worksheet_Change срабатывает также при вставке значения в ячейку, однако оно не срабатывает при пересчете ячейки. При пересчете ячейки срабаты& вает событие Worksheet_Calculate. Практикум Рассмотрим задачу введения времени отправки и прибытия авиарейсов в 24& часовом формате (например, 23:45). Ее можно упростить, поручив Excel ввод символа двоеточия. Вот как это сделать с помощью события Worksheet_Change: Private Sub Worksheet_Change(ByVal Target As Range) Dim ThisColumn As Integer Dim UserInput As String, NewInput As String ThisColumn = Target.Column If ThisColumn < 3 Then UserInput = Target.Value If IsNumeric(UserInput) Then If UserInput > 1 Then NewInput = Left(UserInput, Len(UserInput) - 2) _ & ":" & Right(UserInput, 2) Application.EnableEvents = False Target = NewInput Application.EnableEvents = True End If End If End If End Sub Приведенный выше код будет автоматически преобразовывать числа, введенные в ячейки столбцов A и B рабочего листа (If ThisColumn < 3), во время в 24& часовом формате (например, 2345 в 23:45). Внимание Чтобы избежать зацикливания в результате изменения содержимого ячейки, запретите обработку событий с помощью строки Application.EnableEvents = False. События Глава 8 203 Событие Worksheet_SelectionChange(ByVal Target As Range) Событие Worksheet_SelectionChange срабатывает при выделении ноY вого диапазона ячеек. Target — это объект выделенного диапазона ячеек. Приведенный ниже код применяется для изменения цвета заливки столбца и строки, на пересечении которых находится выделенная ячейка: Private Sub Worksheet_SelectionChange(ByVal Target As Range) Dim iColor As Integer On Error Resume Next iColor = Target.Interior.ColorIndex If iColor < 0 Then iColor = 36 Else iColor = iColor + 1 End If If iColor = Target.Font.ColorIndex Then iColor = iColor + 1 Cells.FormatConditions.Delete With Range("A" & Target.Row, Target.Address) ' В англоязычной версии Excel: ' .FormatConditions.Add Type:=2, Formula1:="TRUE" .FormatConditions.Add Type:=2, Formula1:="ИСТИНА" .FormatConditions(1).Interior.ColorIndex = iColor End With With Range(Target.Offset(1 - Target.Row, 0).Address & ":" & _ Target.Offset(-1, 0).Address) ' В англоязычной версии Excel: ' .FormatConditions.Add Type:=2, Formula1:="TRUE" .FormatConditions.Add Type:=2, Formula1:="ИСТИНА" .FormatConditions(1).Interior.ColorIndex = iColor End With End Sub Внимание Указанный код обработки события Worksheet_SelectionChange отменяет лю& бое условное форматирование, заданное на рабочем листе. Кроме того, он может очистить буфер обмена, затрудняя тем самым выполнение операций копирования и вставки в пределах данного рабочего листа. Событие Worksheet_FollowHyperlink(ByVal Target As Hyperlink) Событие Worksheet_FollowHyperlink срабатывает при щелчке на гиY перссылке. Target — это объект гиперссылки. 204 Часть I Первые шаги События листа диаграммы События листа диаграммы срабатывают при изменении или активизации диаграммы. Для доступа к событиям встроенных диаграмм используются моY дули классов. См. также Более подробно модули классов рассматриваются в главе 20, “Создание пользо& вательских объектов, типов и коллекций”. Ниже приведен список событий, существующих на уровне листа диаграмY мы Excel: Activate; BeforeDoubleClick; BeforeRightClick; Calculate; Deactivate; DragOver; DragPlot; MouseDown; MouseMove; MouseUp; Resize; Select; SeriesChange. Встроенные диаграммы Поскольку встроенные диаграммы не имеют отдельного листа, для доступа к их событиям необходимо использовать модуль класса. 1. Добавьте к проекту модуль класса. 2. Переименуйте его в cl_ChartEvents. 3. Разместите в модуле класса следующую строку кода: Public WithEvents myChartClass As Excel.Chart Это сделает события встроенной диаграммы доступными через модуль класса, как показано на рис. 8.6. 4. Добавьте к проекту стандартный модуль. 5. Разместите в стандартном модуле следующие строки кода: События Глава 8 205 Dim myClassModule As New cl_ChartEvents Sub InitializeChart() Set myClassModule.myChartClass = _ Worksheets(1).ChartObjects(1).Chart End Sub Рис. 8.6. События встроенной диаграммы доступны посредством модуля класса Тем самым вы сделаете объект встроенной диаграммы доступным как обычный объект диаграммы. Процедуру InitializeChart необходиY мо выполнить один раз при открытии рабочей книги (воспользуйтесь событием Workbook_Open). Внимание Первая часть имени события встроенной диаграммы будет совпадать с именем объекта диаграммы, заданном в модуле класса (в данном случае — myChartClass). Событие Chart_Activate() Событие Chart_Activate срабатывает при активизации листа диаграммы. Событие Chart_BeforeDoubleClick(ByVal ElementID As Long, ByVal Arg1 As Long, ByVal Arg2 As Long, Cancel As Boolean) Событие Chart_BeforeDoubleClick срабатывает в результате двойного щелчка на любом элементе диаграммы. ElementID — это объект, представY ляющий элемент диаграммы, на котором был сделан двойной щелчок (например, легенду). Параметры Arg1 и Arg2 зависят от значения параметра ElementID. Чтобы запретить выполнение стандартного действия, предприY нимаемого при обработке двойного щелчка, установите значение параметра Cancel равным True. Согласно приведенному ниже коду, двойной щелчок на легенде диаграммы приводит к скрытию легенды. Чтобы вернуть легенду на место, дважды щелкY ните на области диаграммы. Private Sub myChartClass_BeforeDoubleClick(ByVal ElementID As _ Long, ByVal Arg1 As Long, ByVal Arg2 As Long, Cancel As Boolean) Select Case ElementID 206 Часть I Первые шаги Case xlLegend myChartClass.HasLegend = False Cancel = True Case xlChartArea myChartClass.HasLegend = True Cancel = True End Select End Sub Событие Chart_BeforeRightClick(Cancel As Boolean) Событие Chart_BeforeRightClick срабатывает в результате щелчка на диаграмме правой кнопкой мыши. Чтобы запретить выполнение стандартного действия, предпринимаемого при обработке щелчка правой кнопкой мыши, установите значение параметра Cancel равным True. Событие Chart_Calculate() Событие Chart_Calculate срабатывает при изменении исходных данY ных диаграммы. Событие Chart_Deactivate() Событие Chart_Deactivate срабатывает при деактивизации листа диаY граммы. Событие Chart_DragOver() Событие Chart_DragOver срабатывает при перетаскивании диапазона ячеек на диаграмму. Событие Chart_DragPlot() Событие Chart_DragPlot срабатывает в результате перетаскивания с поY следующим опусканием диапазона ячеек на диаграмму. Событие Chart_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_MouseDown срабатывает в результате нажатия любой кнопки мыши при условии, что указатель мыши находится над диаграммой. Параметр Button определяет кнопку мыши, которая была нажата; параметр Shift — были ли нажаты при этом кнопки <Shift>, <Ctrl> и <Alt>; параметр x — координату указателя мыши по горизонтали; параметр y — координату указателя мыши по вертикали. Рассмотрим пример. При щелчке на диаграмме левой кнопкой мыши знаY чения по оси Y сместятся на 50 единиц вверх, а при щелчке правой кнопкой мыши YYYY на 50 единиц вниз. Для запрета выполнения стандартного действия, События Глава 8 207 предпринимаемого при обработке щелчка правой кнопкой мыши (в данном случае YYYY отображения контекстного меню) используется параметр Cancel события Chart_BeforeRightClick. Private Sub myChartClass_MouseDown(ByVal Button As Long, ByVal _ Shift As Long, ByVal x As Long, ByVal y As Long) If Button = 1 Then ActiveChart.Axes(xlValue).MaximumScale = _ ActiveChart.Axes(xlValue).MaximumScale - 50 End If If Button = 2 Then ActiveChart.Axes(xlValue).MaximumScale = _ ActiveChart.Axes(xlValue).MaximumScale + 50 End If End Sub Событие Chart_MouseMove(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_MouseMove срабатывает при перемещении указателя мыши над диаграммой. Параметр Button определяет кнопку мыши, которая могла быть при этом нажата; параметр Shift — были ли нажаты кнопки <Shift>, <Ctrl> и <Alt>; параметр x — координату указателя мыши по гориY зонтали; параметр y — координату указателя мыши по вертикали. Событие Chart_MouseUp(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Событие Chart_MouseUp срабатывает в результате отпускания нажатой кнопки мыши при условии, что указатель мыши находится над диаграммой. Параметр Button определяет кнопку мыши, которая была нажата, а затем отY пущена; параметр Shift — были ли нажаты при этом кнопки <Shift>, <Ctrl> и <Alt>; параметр x — координату указателя мыши по горизонтали; параметр y — координату указателя мыши по вертикали. Событие Chart_Resize() Событие Chart_Resize срабатывает при изменении размера диаграммы. Если диаграмма находится на отдельном листе, событие Chart_Resize сраY батывает при условии установки параметра Вид По размеру окна (View Sized with Window) в главном меню Excel. Событие Chart_Select(ByVal ElementID As Long, ByVal Arg1 As Long, ByVal Arg2 As Long) Событие Chart_Select срабатывает при выделении элемента диаграмY мы. ElementID — это объект, представляющий выделенный элемент диаY 208 Часть I Первые шаги граммы (например, легенда). Параметры Arg1 и Arg2 зависят от значения параметра ElementID. В соответствии со следующим кодом выделение элемента диаграммы приY водит к выделению соответствующего этому элементу набора данных: Private Sub myChartClass_Select(ByVal ElementID As Long, _ ByVal Arg1 As Long, ByVal Arg2 As Long) If Arg1 = 0 Then Exit Sub Sheets("Встроенная диаграмма").Cells.Interior.ColorIndex = xlNone If ElementID = 3 Then If Arg2 = -1 Then ' Выделить всю последовательность данных. Sheets("Встроенная диаграмма").Range("A2:A22").Offset(0, _ Arg1).Interior.ColorIndex = 19 Else ' Выделить значение, соответствующее указанному маркеру. Sheets("Встроенная диаграмма").Range("A1").Offset(Arg2, _ Arg1).Interior.ColorIndex = 19 End If End If End Sub Событие Chart_SeriesChange(ByVal SeriesIndex As Long, ByVal PointIndex As Long) Событие Chart_SeriesChange срабатывает при обновлении ряда данY ных на диаграмме. SeriesIndex — это смещение в коллекции Series обновленного ряда данных, PointIndex — смещение в коллекции Points обновленной точки данных. События приложения События приложения охватывают все рабочие книги, открытые в рамках текущего сеанса работы с Excel. Для доступа к событиям приложения необхоY димо использовать модуль класса (в этом смысле события приложения имеют много общего с событиями встроенной диаграммы). 1. Добавьте к проекту модуль класса. 2. Переименуйте его в cl_AppEvents. 3. Разместите в модуле класса следующую строку кода: Public WithEvents AppEvent As Application Это сделает события приложения доступными через модуль класса, как показано на рис. 8.7. 4. Добавьте к проекту стандартный модуль. События Глава 8 209 Рис. 8.7. События приложения доступны посредством модуля класса 5. Разместите в стандартном модуле следующие строки кода: Dim myAppEvent As New cl_AppEvents Sub InitializeAppEvent() Set myAppEvent.AppEvent = Application End Sub Приведенный выше код делает объект AppEvent доступным как объект приложения. Процедуру InitializeAppEvent необходимо выполY нить один раз при открытии рабочей книги (воспользуйтесь событием Workbook_Open). Внимание Первая часть имени события приложения будет совпадать с именем объекта при& ложения, заданном в модуле класса (в данном случае — AppEvent). Ниже приведен список событий, существующих на уровне приложения Excel: NewWorkbook; SheetActivate; SheetBeforeDoubleClick; SheetBeforeRightClick; SheetCalculate; SheetChange; SheetDeactivate; SheetFollowHyperlink; SheetSelectionChange; WindowActivate; WindowDeactivate; WindowResize; WorkbookActivate; 210 Часть I Первые шаги WorkbookAddinInstall; WorkbookAddinUninstall; WorkbookBeforeClose; WorkbookBeforePrint; WorkbookBeforeSave; WorkbookDeactivate; WorkbookNewSheet; WorkbookOpen. Событие AppEvent_NewWorkbook(ByVal Wb As Workbook) Событие AppEvent_NewWorkbook срабатывает при создании новой рабоY чей книги. Wb — это объект созданной рабочей книги. Следующий код упоряY дочивает на экране окна открытых рабочих книг: Private Sub AppEvent_NewWorkbook(ByVal Wb As Workbook) Application.Windows.Arrange xlArrangeStyleTiled End Sub Событие AppEvent_SheetActivate(ByVal Sh As Object) Событие AppEvent_SheetActivate срабатывает при активизации люY бого рабочего листа или листа диаграммы. Sh — это объект активизированY ного рабочего листа или листа диаграммы. Событие AppEvent_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие AppEvent_SheetBeforeDoubleClick срабатывает в результаY те двойного щелчка на любом рабочем листе. Sh — это объект активного рабоY чего листа, Target — объект выделенного диапазона ячеек. Чтобы запретить выполнение стандартного действия, предпринимаемого при обработке двойY ного щелчка, установите значение параметра Cancel равным True (по умолY чанию значение параметра Cancel равно False). Событие AppEvent_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean) Событие AppEvent_SheetBeforeRightClick срабатывает в результате щелчка правой кнопкой мыши на любом рабочем листе. Sh — это объект акY тивного рабочего листа, Target — объект, на котором был выполнен щелчок правой кнопкой мыши. Чтобы запретить выполнение стандартного действия, предпринимаемого при обработке щелчка правой кнопкой мыши, установите значение параметра Cancel равным True. События Глава 8 Событие AppEvent_SheetCalculate(ByVal Sh As Object) Событие AppEvent_SheetCalculate срабатывает при пересчете любого рабочего листа или изменении данных любой диаграммы. Sh — это объект акY тивного листа. Событие AppEvent_SheetChange(ByVal Sh As Object, ByVal Target As Range) Событие AppEvent_SheetChange срабатывает при изменении содержиY мого любой ячейки. Sh — это объект рабочего листа, Target — объект измеY ненного диапазона ячеек. Событие AppEvent_SheetDeactivate(ByVal Sh As Object) Событие AppEvent_SheetDeactivate срабатывает при деактивизации любого рабочего листа или листа диаграммы. Sh — это объект деактивизироY ванного листа. Событие AppEvent_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As Hyperlink) Событие AppEvent_SheetFollowHyperlink срабатывает при щелчке на любой гиперссылке. Sh — это объект активного рабочего листа, Target — объект гиперссылки. Событие AppEvent_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) Событие AppEvent_SheetSelectionChange срабатывает при выделеY нии нового диапазона ячеек на любом листе. Sh — это объект активного лисY та, Target — объект выделенного диапазона ячеек. Событие AppEvent_WindowActivate(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WindowActivate срабатывает при активизации окY на любой рабочей книги. Wb — это объект рабочей книги, отображаемой в акY тивизированном окне, Wn — объект окна. Событие AppEvent_WindowDeactivate(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WindowDeactivate срабатывает при деактивизации окна любой рабочей книги. Wb — это объект рабочей книги, отображаемой в деактивизированном окне, Wn — объект окна. 211 212 Часть I Первые шаги Событие AppEvent_WindowResize(ByVal Wb As Workbook, ByVal Wn As Window) Событие AppEvent_WindowResize срабатывает при изменении размеров окна активной рабочей книги. Wb — это объект активной рабочей книги, Wn — объект окна. Внимание Запрет изменения размера окна рабочей книги (EnableResize = False) при& водит к удалению кнопок окна Свернуть (Minimize) и Развернуть (Maximize). Что& бы вернуть кнопки на место, выполните в окне Immediate (Быстрое выполнение) строку ActiveWindow.EnableResize = True. Событие AppEvent_WorkbookActivate(ByVal Wb As Workbook) Событие AppEvent_WorkbookActivate срабатывает при активизации любой рабочей книги. Wb — это объект активизированной рабочей книги. Следующий код указывает на необходимость развернуть окно рабочей книги при ее активизации: Private Sub AppEvent_WorkbookActivate(ByVal Wb As Workbook) Wb.WindowState = xlMaximized End Sub Событие AppEvent_WorkbookAddinInstall(ByVal Wb As Workbook) Событие AppEvent_WorkbookAddinInstall срабатывает при установке рабочей книги в качестве надстройки (чтобы установить надстройку, выбериY те команду меню Excel Сервис Надстройки (Tools AddYIns)). Обратите внимание, что событие AppEvent_WorkbookAddinInstall не срабатывает при двойном щелчке на значке файла надстройки (файл с расширением .xla). Wb — это объект рабочей книги, установленной в качестве надстройки. Событие AppEvent_WorkbookAddinUninstall(ByVal Wb As Workbook) Событие AppEvent_WorkbookAddinUninstall срабатывает при удалеY нии рабочей книги, используемой в качестве надстройки. Обратите внимание, что надстройка не закрывается автоматически. Wb YYYY это объект удаляемой рабочей книги, использовавшейся в качестве надстройки. События Глава 8 213 Событие AppEvent_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean) Событие AppEvent_WorkbookBeforeClose срабатывает при закрытии рабочей книги. Wb — это объект рабочей книги. Чтобы запретить закрытие раY бочей книги, установите значение параметра Cancel равным True. Событие AppEvent_WorkbookBeforePrint(ByVal Wb As Workbook, Cancel As Boolean) Событие AppEvent_WorkbookBeforePrint срабатывает при попытке печати рабочей книги (способ инициирования процесса печати YYYY с помощью команды меню, кнопки панели инструментов, комбинации клавиш или проY граммного кода YYYY не играет роли). Wb — это объект рабочей книги. Чтобы заY претить печать, установите значение параметра Cancel равным True. Следующий код помещает имя пользователя в нижний колонтитул печаY таемых страниц: Private Sub AppEvent_WorkbookBeforePrint(ByVal Wb As Workbook, _ Cancel As Boolean) Wb.ActiveSheet.PageSetup.LeftFooter = Application.UserName End Sub Событие AppEvent_WorkbookBeforeSave(ByVal Wb As Workbook, ByVal SaveAsUI As Boolean, Cancel As Boolean) Событие AppEvent_WorkbookBeforeSave срабатывает при попытке соY хранения рабочей книги. Wb — это объект рабочей книги. Чтобы отобразить диалоговое окно Сохранение документа (Save As), установите значение параY метра SaveAsUI равным True. Чтобы запретить сохранение рабочей книги, установите значение параметра Cancel равным True. Событие AppEvent_WorkbookDeactivate(ByVal Wb As Workbook) Событие AppEvent_WorkbookDeactivate срабатывает при деактивизаY ции любой рабочей книги. Wb — это объект деактивизированной рабочей книги. Событие AppEvent_WorkbookNewSheet(ByVal Wb As Workbook, By Val Sh As Object) Событие AppEvent_WorkbookNewSheet срабатывает при добавлении в активную рабочую книгу нового листа. Wb — это объект активной рабочей книги, Sh — объект нового рабочего листа или листа диаграммы. 214 Часть I Первые шаги Событие AppEvent_WorkbookOpen(ByVal Wb As Workbook) Событие AppEvent_WorkbookOpen срабатывает при открытии рабочей книги. Wb — это объект только что открытой рабочей книги. Следующий шаг В этой главе были рассмотрены события, которые позволяют реагировать на всевозможные действия, происходящие в пределах текущего сеанса рабоY ты с Excel. Следующая глава посвящена взаимодействию Excel с пользоватеY лем. В частности, в ней будут рассмотрены такие вопросы, как запрос инY формации у пользователя, вывод предупреждающих сообщений, а также предоставление графического интерфейса для реализации дополнительных способов взаимодействия. Глава 9 Ââåäåíèå â ïîëüçîâàòåëüñêèå ôîðìû Способы взаимодействия с пользователем В отличие от окна ввода и окна соY общения, формы Excel выводят взаиY модействие с пользователем на соверY шенно иной качественный уровень. В этой главе будут рассмотрены базовые интерфейсы взаимодействия с пользователем, такие как окно ввоY да и окно сообщения, а также основы создания пользовательских форм. Более подробно пользовательские формы рассматриваются в главе 21, ‘‘Пользовательские формы YYYY проY фессиональный подход’’. Окно ввода Окно ввода данных представляет собой один из основных интерфейсов взаимодействия с пользователем. НаY страиваемым является текст сообщеY ния, заголовок окна, значение по умолчанию, положение окна на экраY не, а также файл справки. Окно ввода имеет две кнопки YYYY OK и Отмена (Cancel). Функция InputBox возвраY щает значение типа String. В резульY тате выполнения приведенного ниже кода пользователю будет предложено ввести количество месяцев, для котоY рого необходимо подсчитать некотоY рые статистические данные: 9 Способы взаимодействия с пользователем ........................... 215 Создание пользовательской формы ............................................ 216 Вызов и скрытие пользовательской формы.......... 218 Основные элементы управления формы..................... 220 Использование вкладок для объединения форм......................225 Следующий шаг........................... 226 216 Часть I Первые шаги AveMonths = InputBox(Prompt:="Введите количество месяцев", _ Title:="Введите количество месяцев", Default:="3") Соответствующее окно ввода показано на рис. 9.1. Рис. 9.1. Простейшее окно ввода данных Окно сообщения Не менее важным интерфейсом взаимодействия с пользователем является окно сообщения. В отличие от окна ввода, окно сообщения может иметь люY бую комбинацию из кнопок Да (Yes), Нет (No), OK и Отмена (Cancel). НаY страиваемым также является текст сообщения, заголовок окна и файл справY ки. В результате выполнения приведенного ниже кода пользователю будет предложено закрыть рабочую книгу и сохранить внесенные в нее изменения: MyMsg = "Сохранить изменения?" MyTitle = "Закрытие рабочей книги" Response = MsgBox(MyMsg, vbExclamation + vbYesNoCancel, MyTitle) Select Case Response Case Is = vbYes ActiveWorkbook.Close SaveChanges:=False Case Is = vbNo ActiveWorkbook.Close SaveChanges:=True Case Is = vbCancel Exit Sub End Select Выражение Select Case используется для принятия решения относительY но хода выполнения программного кода в зависимости от сделанного пользоваY телем выбора. Соответствующее окно сообщения показано на рис. 9.2. Рис. 9.2. Окно сообщения — это один из способов базового взаимодействия с пользователем Создание пользовательской формы По сравнению с окном ввода и окном сообщения пользовательская форма имеет гораздо более широкие возможности. К примеру, ее можно применять для ввода пользователем личной информации, как показано на рис. 9.3. Введение в пользовательские формы Глава 9 217 Чтобы добавить к проекту пользовательскую форму, выберите команду меY ню редактора Visual Basic Insert UserForm (Вставить Пользовательская форма). В результате этого к проекту будет добавлен модуль новой формы, на месте области ввода программного кода будет отображена пустая форма и на экране появится окно панели инструментов, как показано на рис. 9.4. Рис. 9.3. Пример пользо& вательской формы Рис. 9.4. Пустая пользовательская форма и панель инструментов Изменить размер формы можно с помощью маркеров, расположенных по бокам и в углах окна формы. Чтобы добавить на форму элемент управления, щелкните на соответствующей ему кнопке на панели инструментов и нариY суйте его на форме. Помещенные на форму элементы управления можно пеY ремещать, а также изменять их размер. На заметку По умолчанию на панели инструментов находятся только наиболее часто используе& мые элементы управления. Чтобы получить доступ к остальным элементам управления, щелкните на панели инструментов правой кнопкой мыши и выберите команду контек& стного меню Additional Controls (Дополнительные элементы управления). После добавления элемента управления на форму его свойства становятся досY тупны посредством окна свойств редактора Visual Basic. Свойства элемента управY ления можно настроить как вручную, так и с помощью программного кода. Совет Пользовательской форме, а также элементам управления рекомендуется назна& чать описательные имена. Примером стандартного имени пользовательской формы является имя UserForm1. Измените его на что&нибудь более значащее, например, frm_AddEmp. 218 Часть I Первые шаги Вызов и скрытие пользовательской формы Пользовательскую форму можно вызвать из любого модуля. Следующий код выводит на экран форму frm_AddEmp: frm_AddEmp.Show Для вызова формы может применяться также метод Load, однако в этом случае форма будет лишь загружена, но не выведена на экран. Для скрытия формы применяется метод Hide. Форма будет поYпрежнему активна, однако не видна на экране. Все элементы управления формы доступY ны посредством программного кода. Метод Unload выгружает форму из памяти и убирает ее с экрана. В резульY тате выполнения приведенного ниже кода форма Me становится недоступной как посредством пользовательского интерфейса, так и посредством проY граммного кода: Unload Me Программирование пользовательской формы Код обработки событий элементов управления формы помещается в моY дуль этой формы. В отличие от других модулей, двойной щелчок на модуле формы приводит к ее открытию в режиме конструктора. Чтобы просмотреть код формы, щелкните правой кнопкой мыши на названии модуля формы или на самой форме в режиме конструктора и выберите команду контекстного меY ню View Code (Просмотр кода). Чтобы ввести код обработки стандартного события элемента управления формы, выделите требуемый элемент управления и выберите команду меню View Code (Вид Код). Редактор Visual Basic автоматически сгенерирует заY головки процедуры обработки стандартного события. Чтобы отобразить спиY сок других событий данного элемента управления, выберите соответствуюY щий ему объект из раскрывающегося списка Object (Объект) и откройте расY крывающийся список Properties (Свойства), как показано на рис. 9.5. Рис. 9.5. Раскрывающийся список Properties содержит все события, доступные для элемента управления формы, выбранного из списка Object Введение в пользовательские формы Глава 9 219 Все элементы управления являются объектами с соответствующими свойY ствами и методами. Обычно весь код, относящийся к элементу управления, размещается в модуле формы. При обращении к элементу управления извне этого модуля имя соответствующего объекта необходимо предварить именем объекта формы. Private Sub btn_EmpCancel_Click() Unload Me End Sub Рассмотрим некоторые из составляющих элементов приведенного выше кода: btn_EmpCancel — имя элемента управления; Click — событие элемента управления; Unload Me — код обработки события элемента управления (в данном случае форма будет убрана с экрана и выгружена из памяти). Практикум Добавление элемента управления к существующей форме Добавление элемента управления к существующей форме представляет собой весьма непростую задачу. Щелкнув на новом элементе управления правой кноп& кой мыши и выбрав команду контекстного меню View Code (Просмотр кода), вы обнаружите, что редактор Visual Basic даже не подозревает о существовании та& кого элемента. Имени объекта элемента управления нет и в раскрывающемся спи& ске Object (Объект). Чтобы добавить элемент управления к существующей форме, выполните следую& щие действия. 1. Добавьте к существующей форме все необходимые элементы управления. 2. Щелкните на названии модуля формы в диспетчере проектов правой кнопкой мыши и выберите команду контекстного меню Export File (Экспорт в файл). В открывшемся диалоговом окне Export File (Экспорт в файл) щелкните на кнопке Сохранить (Save), чтобы сохранить файл формы в стандартном разме& щении. 3. Снова щелкните на названии модуля формы в диспетчере проектов правой кноп& кой мыши и выберите команду контекстного меню Remove (Удалить). В ответ на предложение экспортировать файл формы щелкните на кнопке Нет (No). 4. Щелкните правой кнопкой мыши на незанятом участке окна диспетчера про& ектов и выберите из контекстного меню команду Import File (Импорт из файла). Выберите созданный выше файл формы и щелкните на кнопке Открыть (Open). В результате выполнения приведенной выше последовательности действий ре& дактор Visual Basic распознает все элементы управления, добавленные к сущест& вующей форме. 220 Часть I Первые шаги Основные элементы управления формы Форма, показанная на рис. 9.6, состоит из надписей, полей ввода и командных кнопок. После ввода требуемой информации пользователь щелкает на кнопке OK, что приводит к заполнению соответствующих ячеек на рабочем листе, как поY казано на рис. 9.7. Рис. 9.6. Простая форма, пред& назначенная для ввода пользо& вательских данных Рис. 9.7. Информация, введенная в форму, помещает& ся на рабочий лист Excel Private Sub btn_EmpOK_Click() Dim LastRow As Long LastRow = Worksheets("Лист2").Range("A65536").End(xlUp).Row + 1 Cells(LastRow, 1).Value = tb_EmpName.Value Cells(LastRow, 2).Value = tb_EmpPosition.Value Cells(LastRow, 3).Value = tb_EmpHireDate.Value End Sub Примечательно, что одна и та же форма может использоваться как для ввода, так и для извлечения информации. Чтобы приспособить показанную на рис. 9.6 форму для извлечения сведений о должности служащего и дате приема на рабоY ту, измените код обработки события btn_EmpOK_Click, как показано ниже: Private Sub btn_EmpOK_Click() Dim EmpFound As Range With Range("EmpList") Set EmpFound = .Find(tb_EmpName.Value) If EmpFound Is Nothing Then MsgBox("Служащий не найден!") tb_EmpName.Value = "" Exit Sub Else With Range(EmpFound.Address) tb_EmpPosition = .Offset(0, 1) tb_HireDate = .Offset(0, 2) End With End If End With End Sub Введение в пользовательские формы Глава 9 221 Использование списков и комбинированных списков При вводе имени служащего можно допустить ошибку. Чтобы этого не произошло, предложите пользователю выбрать имя служащего из списка или комбинированного списка. Список позволяет выбрать одно или несколько значений. Комбинированный список позволяет выбрать одно значение или ввести новое. Заменим поле ввода имени служащего списком, как показано на рис. 9.8. Элементы списка определяются значением свойства RowSource объекта списка. Поскольку список служащих может меняться, для обращения к нему рекомендуется использовать динамический именованный диапазон ячеек. Private Sub btn_EmpOK_Click() Dim EmpFound As Range With Range("EmpList") Set EmpFound = .Find(lb_EmpName.Value) If EmpFound Is Nothing Then MsgBox("Служащий не найден!") lb_EmpName.Value = "" Exit Sub Else With Range(EmpFound.Address) tb_EmpPosition = .Offset(0, 1) tb_HireDate = .Offset(0, 2) End With End If End With End Sub Выбор нескольких значений из списка Объект списка имеет свойство MultiSelect, позволяющее выбирать из списка несколько значений одновременно (рис. 9.9). Рис. 9.8. Список позволяет избе& жать ошибок ввода Рис. 9.9. Список поддерживает возможность выбора нескольких значений одновременно 222 Часть I Первые шаги Ниже перечислены возможные значения этого свойства: fmMultiSelectSingle — значение по умолчанию, разрешающее выY бор из списка только одного элемента; fmMultiSelectMulti — разрешает выбор из списка нескольких элеY ментов одновременно, а также отмену выбора элемента путем повторY ного щелчка на нем; fmMultiSelectExtended — разрешает выбор из списка нескольких элементов одновременно с применением клавиш <Ctrl> и <Shift>, а также отмену выбора элемента путем повторного щелчка на нем. Следующий код демонстрирует способ обращения к элементам списка, поддерживающего множественный выбор: Private Sub btn_EmpOK_Click() Dim LastRow As Long, i As Integer LastRow = Worksheets("Лист2").Range("A65536").End(xlUp).Row + 1 Cells(LastRow, 1).Value = tb_EmpName.Value 'Какие элементы из списка выбраны? For i = 0 To lb_EmpPosition.ListCount - 1 'Если элемент выбран, добавить его на рабочий лист. If lb_EmpPosition.Selected(i) = True Then Cells(LastRow, 2).Value = Cells(LastRow, 2).Value & _ lb_EmpPosition.List(i) & "," End If Next i Cells(LastRow, 3).Value = tb_HireDate.Value End Sub Поскольку первый элемент списка имеет порядковый номер 0, при опреY делении верхней границы значения переменнойYсчетчика от значения свойY ства ListCount необходимо отнять 1: For i = 0 To lb_EmpPosition.ListCount - 1 Использование переключателей Переключатели должны быть отделены от остальных элеменY тов пользовательской формы с помощью панели, как показано на рис. 9.10. Все переключатели одной группы должны иметь одинаковое значение свойства GroupName, чтобы гарантировать возможность выбора только одY ного переключателя в группе. Совет Некоторые пользователи предпочитают выбирать переключатель путем щелчка на соответствующей ему надписи (рис. 9.11). Ниже приведен пример кода, реали& зующего такую возможность: Private Sub Lbl_Bldg1_Click() Obtn_Bldg1.Value = True End Sub Введение в пользовательские формы Рис. 9.10. Для группирования пере& ключателей используется панель Глава 9 223 Рис. 9.11. Некоторые пользователи предпочитают выбирать переклю& чатель путем щелчка на соответст& вующей надписи Использование изображений Изображения позволяют придать списку более наглядный вид, как показано на рис. 9.12. Рис. 9.12. Изображения придают списку более на& глядный вид Согласно приведенному ниже коду, выбор имени служащего из списка буY дет сопровождаться выводом фотографии этого служащего: Private Sub lb_EmpName_Change() Dim EmpFound As Range With Range("EmpList") Set EmpFound = .Find(lb_EmpName.Value) If EmpFound Is Nothing Then MsgBox("Служащий не найден!") lb_EmpName.Value = "" Exit Sub Else 224 Часть I Первые шаги With Range(EmpFound.Address) tb_EmpPosition = .Offset(0, 1) tb_HireDate = .Offset(0, 2) On Error Resume Next Img_Employee.Picture = LoadPicture _ (ThisWorkbook.Path & Application.PathSeparator & EmpFound & ".jpg") On Error GoTo 0 End With End If End With End Sub Использование счетчиков Как показано на рис. 9.12, поле Дата приема на работу позволяет вводить данные в произвольном формате, например, 1/1/1 или 1 января 2001 года. Чтобы унифицировать ввод даты приема служащего на работу, следует восY пользоваться счетчиками. Счетчик ограничивает пользователя вводом численного значения и содержит кнопки увеличения и уменьшения последнего. Рассмотрим создание счетчика для ввода месяца. Поместите счетчик на форму. Установите свойство объекта счетчика Min равным 1 (январь), свойство Max YYYY 12 (декабрь), свойство Value YYYY 1 (начальное значение). Разместите рядом со счетчиком поле ввода, которое будет использоваться для отображения текущего значения счетчика (вместо поля ввода можно примеY нить надпись). Ниже приведен код обработки события, срабатывающего при изменении значения счетчика: Private Sub SpBtn_Month_Change() tb_Month.Value = SpBtn_Month.Value End Sub Добавьте на форму еще два счетчика и два поля ввода, как показано на рис. 9.13. Private Sub btn_EmpOK_Click() Dim LastRow As Long, i As Integer LastRow = Worksheets("Лист2").Range("A65536").End(xlUp).Row + 1 Cells(LastRow, 1).Value = tb_EmpName.Value For i = 0 To lb_EmpPosition.ListCount - 1 If lb_EmpPosition.Selected(i) = True Then Cells(LastRow, 2).Value = Cells(LastRow, 2).Value & _ lb_EmpPosition.List(i) & "," End If Next i 'Создание даты путем конкатенации значений полей ввода. Cells(LastRow, 3).Value = tb_Month.Value & "/" & _ tb_Day.Value & "/" & tb_Year.Value End Sub Введение в пользовательские формы Глава 9 225 Использование вкладок для объединения форм Вкладки позволяют объединить воедино несколько различных форм. На рис. 9.14 показан пример объединения форм для ввода служебной и личной информации о сотруднике. Рис. 9.13. Чтобы унифициро& вать ввод даты приема слу& жащего на работу, восполь& зуйтесь счетчиками Рис. 9.14. Используйте вкладки для объединения несколь& ких различных форм Совет Старайтесь планировать формы с вкладками заранее. Чтобы создать форму с вкладками на основе уже существующих форм, необходимо создать новую фор& му, добавить в нее требуемое число вкладок и скопировать на них элементы управления с имеющихся форм. Проверка ввода обязательных данных Одним из преимуществ электронной формы является возможность проY верки ввода обязательных данных: If tb_EmpName.Value = "" Then frm_AddEmp.Hide MsgBox("Пожалуйста, введите имя служащего") frm_AddEmp.Show Exit Sub End If Закрытие формы Как и большинство окон Windows, окно пользовательской формы имеет кнопку Закрыть (Close) (кнопка с изображением крестика), расположенную в его правом верхнем углу. В зависимости от предназначения формы ее закрыY тие путем щелчка на этой кнопке может оказаться весьма нежелательным. Определить способ закрытия окна формы и, при необходимости, соответстY вующим образом среагировать на него поможет событие QueryClose: 226 Часть I Первые шаги Private Sub UserForm_QueryClose(Cancel As Integer, _ CloseMode As Integer) If CloseMode = vbFormControlMenu Then MsgBox "Для закрытия формы щелкните на кнопке OK _ или Отмена", vbCritical Cancel = True End If End Sub Согласно приведенному выше коду попытка закрытия формы с помощью недозволенного способа приведет к выводу окна сообщения, показанного на рис. 9.15. Рис. 9.15. Событие QueryClose позволяет опре& делить способ закрытия окна формы и соответ& ствующим образом среагировать на него Ниже перечислены оставшиеся способы закрытия окна формы: vbFormCode — форма была закрыта с помощью метода Unload; vbAppWindows — форма была закрыта в результате завершения рабоY ты Windows; vbTaskManager — форма была закрыта с помощью диспетчера задач. Следующий шаг В этой главе были рассмотрены базовые интерфейсы взаимодействия с пользоY вателем, такие как окно ввода и окно сообщения, а также основы создания польY зовательских форм Excel. Следующая глава посвящена диаграммам YYYY одному из наиболее эффективных средств наглядного представления данных. Часть II II Автоматизация Excel 10. Диаграммы .......................................................................... 229 11. Анализ данных с помощью расширенного фильтра ..... 267 12. Сводные таблицы............................................................... 299 13. Excel всемогущий................................................................ 363 14. Взаимодействие с Internet ............................................... 407 15. Поддержка XML в профессиональном выпуске Excel 2003 .............................................................. 427 16. Автоматизация Word ........................................................ 439 Глава 10 Äèàãðàììû Говорят, лучше один раз увидеть, чем сто раз услышать. Наглядное предY ставление данных имеет неоспоримое преимущество перед сухими цифрами. Именно поэтому диаграммы являются неотъемлемой частью всех программ для работы с электронными таблицаY ми, пройдя вместе с ними долгий эвоY люционный путь развития. В этой главе рассматривается исY пользование VBA при выполнении следующих базовых задач: создание встроенных диаграмм и диаграмм, расположенных на отдельном листе; выбор типа диаграммы; изменение типа диаграммы; форматирование, перемещение и удаление диаграммы и ее элеY ментов; создание нестандартных диаY грамм. Одной из особенностей програмY мирования диаграмм в Excel является наличие двух объектных моделей, каждая из которых соответствует разY личному типу диаграмм. Встроенные диаграммы и диаграммы, расположенные на отдельном листе Изначально все диаграммы создаY вались на отдельном листе. В середиY 10 Встроенные диаграммы и диаграммы, расположенные на отдельном листе .................... 229 Создание диаграмм с помощью VBA ...............................232 Использование объектных переменных для упрощения кода ................................................ 236 “Анатомия” диаграммы ............237 Типы диаграмм ............................ 251 Параметры трехмерных и круговых диаграмм.................... 256 Интерактивные диаграммы..... 260 Экспорт диаграммы в файл изображения................................. 261 Удивительные возможности точечных диаграмм ................... 262 Создание нестандартных диаграмм ...................................... 262 Следующий шаг........................... 266 230 Часть II Автоматизация Excel не 1990Yх годов в Excel была добавлена возможность встраивать диаграмму в существующий рабочий лист. Наличие двух различных типов диаграмм вызвало необходимость создания двух объектных моделей. Диаграмме, расположенной на отдельном листе, соY ответствует объект Chart, в то время как для работы со встроенной диаграмY мой следует использовать объект ChartObject. Встроенные диаграммы и контейнер ChartObject Объект ChartObject является своеобразным ‘‘контейнером’’ встроенной диаграммы. Его основное предназначение заключается в обеспечении способа определения размера встроенной диаграммы и ее положения на рабочем лисY те. Эти параметры распространяются на все внедренные в диаграмму объекты, такие как автофигуры и изображения. Откройте любой рабочий лист, содержащий встроенную диаграмму. Щелкните на диаграмме, удерживая нажатой клавишу <Ctrl> или <Shift>. По бокам и в углах области диаграммы появятся круглые маркеры управления размером белого цвета, как показано на рис. 10.1. Рис. 10.1. Чтобы выделить контейнер ChartObject, щелкните на встроен& ной диаграмме, удерживая нажатой клавишу <Ctrl> или <Shift>. Имя кон& тейнера диаграммы появится в поле Имя слева от поля ввода формулы В поле Имя (Name Box) слева от поля ввода формулы появится имя контейY нера встроенной диаграммы. Это имя используется для обращения к объекту ChartObject, как показано ниже: ActiveSheet.ChartObjects("Диаграмма 1").Select Диаграммы Глава 10 231 Чтобы определить смещение контейнера встроенной диаграммы от верхY ней границы рабочего листа, выделите объект ChartObject и введите в окне Immediate (Быстрое выполнение) редактора Visual Basic строку Print Selection.Top. Отмените выделение контейнера диаграммы, после чего щелкните на неY занятом пространстве между границей диаграммы и областью ее построения. По бокам и в углах области диаграммы появятся квадратные маркеры управY ления размером черного цвета (рис. 10.2), что свидетельствует о выделении области диаграммы. Рис. 10.2. Чтобы выделить область диаграммы, щелкните на незанятом пространстве между границей диаграммы и областью ее построения. В по& ле Имя появится стандартное имя области всех диаграмм — Область диаграммы Области всех диаграмм имеют стандартное имя Область диаграммы (Chart Area), которое выводится в поле Имя слева от поля ввода формулы. Ниже приведен код VBA, соответствующий выделению области диаграммы: ActiveSheet.ChartObjects("Диаграмма 1").Activate ActiveChart.ChartArea.Select Чтобы узнать смещение области диаграммы от верхней границы контейнеY ра, выделите область диаграммы и введите в окне Immediate редактора Visual Basic строку Print Selection.Top. Ниже приведен пример изменения цвета области диаграммы, встроенной в рабочий лист Excel: Worksheets("Лист3").ChartObjects("Диаграмма 2").Chart.ChartArea. _ Interior.ColorIndex = 2 232 Часть II Автоматизация Excel Диаграммы, расположенные на отдельном листе При работе с диаграммами, расположенными на отдельном листе, примеY няется объектная модель, отличная от той, что применялась при работе со встроенными диаграммами. В частности, объект диаграммы принадлежит не объекту контейнера, а объекту листа. Ниже приведен пример изменения цвета области диаграммы, расположенной на отдельном листе: Sheets("Диаграмма 2").ChartArea.Interior.ColorIndex = 2 Создание диаграмм с помощью VBA Наиболее простой способ создания диаграммы с помощью пользовательского интерфейса Excel заY ключается в выделении исходных данных и нажаY тии клавиши <F11>. Рассмотрим код, сгенерироY ванный средством записи макросов при построеY нии диаграммы на основе исходных данных, Рис. 10.3. Чтобы создать показанных на рис. 10.3. диаграмму, выделите ис& На рис. 10.4 показана диаграмма, созданная Excel ходные данные и нажмите в результате нажатия клавиши <F11>. клавишу <F11> Рис. 10.4. В результате нажатия клавиши <F11> Excel создаст новую диаграмму, расположенную на отдельном листе Диаграммы Глава 10 233 Ниже приведен код, сгенерированный средством записи макросов: Charts.Add ActiveChart.SetSourceData Source:=Sheets("Лист1").Range("A1:B5") ActiveChart.Location Where:=xlLocationAsNewSheet Проанализируем первую строку кода: Charts.Add Коллекция Charts представляет собой коллекцию всех листов диаграмм в раY бочей книге. Каждая коллекция имеет метод добавления нового элемента YYYY Add. Таким образом, в результате выполнения строки Charts.Add в коллекцию Charts будет добавлен новый лист диаграммы (пока еще пустой). Добавив новый лист диаграммы, Excel автоматически делает его активным. Для обращения к текущей активной диаграмме можно использовать как объект Chart (например, Charts("Диаграмма 1")), так и объект VBA ActiveChart. Внимание Если текущим активным объектом является объект, отличный от диаграммы, по& пытка использования объекта ActiveChart приведет к возникновению ошибки. ActiveChart.SetSourceData Source:=Sheets("Лист4").Range("A1:B5") Вторая строка автоматически сгенерированного кода определяет диапазон исходных данных для диаграммы с помощью метода SetSourceData. СледуY ет отметить, что VBA содержит соответствующие методы для всех действий, которые можно выполнить посредством пользовательского интерфейса. Так, для того чтобы задать диапазон исходных данных диаграммы, необходимо щелкнуть на ней правой кнопкой мыши и выбрать команду контекстного меY ню Исходные данные (Source Data). На экране появится диалоговое окно Исходные данные (Source Data) (рис. 10.5), вкладка Диапазон данных (Data Range) которого позволяет указать диапазон исходных данных. Ниже приведен полный синтаксис метода SetSourceData: SetSourceData(Source, PlotBy) Здесь Source YYYY это ссылка на диапазон ячеек, а PlotBy YYYY константа, принимающая значение xlColumns или xlRows. Ниже приведен пример вызова метода SetSourceData с указанием всех аргументов: ActiveChart.SetSourceData Source:=Sheets("Лист4").Range("A1:B5"), _ PlotBy:=xlColumns Обратите внимание, что автоматически сгенерированный код не содержит арY гумент PlotBy. Вероятно, средство записи макросов сочло возможным опустить его, поскольку структура исходных данных (имена заголовков столбцов в ячейках A1 и B1) предполагает, что ряды данных расположены в столбцах. К сожалению, подобная эффективность является скорее исключением, чем правилом. ActiveChart.Location Where:=xlLocationAsNewSheet 234 Часть II Автоматизация Excel Рис. 10.5. Для определения исходных данных диаграммы можно восполь& зоваться диалоговым окном Исходные данные или методом VBA SetSourceData Приведенная выше строка соответствует последнему шагу мастера создаY ния диаграмм (рис. 10.6). Рис. 10.6. Выбор размещения диаграммы Ниже приведен полный синтаксис метода Location: Location(Where, Name) Диаграммы Глава 10 235 Константа Where может принимать значения xlLocationAsNewSheet, xlLocationAsNewObject и xlLocationAutomatic. Name — это строка, которая определяет имя нового листа, на котором будет размещена диаграмма (параметр Where принимает значение xlLocationAsNewSheet); имя рабочего листа, на котором будет размещена встроенная диаграмY ма (параметр Where принимает значение xlLocationAsNewObject). Как уже отмечалось, средство записи макросов генерирует много избыточY ного кода. Поскольку использование метода Charts.Add подразумевает, что новая диаграмма будет размещаться на отдельном листе, последняя строка сгенерированного кода является лишней и ее можно удалить. Изменение размещения диаграммы Метод Location позволяет изменить тип диаграммы, преобразовав ее из встроенной на размещенную на отдельном листе и наоборот. (Чтобы изменить размещение диаграммы с помощью пользовательского интерфейса, щелкните на диаграмме правой кнопкой мыши и выберите команды контекстного меню Размещение (Location).) Рассмотрим следующий код: Worksheets("Лист1").ChartObjects("Диаграмма 1").Activate ActiveChart.Location Where:=xlLocationAsNewSheet, Name:="МояДиаграмма" Его выполнение приведет к преобразованию диаграммы Диаграмма 1, встроенной в рабочий лист Лист1, в диаграмму, размещенную на отдельном листе МояДиаграмма. Стандартный тип диаграмм Обратите внимание, что в автоматически сгенерированном коде не был указан тип создаваемой диаграммы. Одна из особенностей Excel VBA заклюY чается в возможности неявного использования стандартных значений параY метров Excel (в данном случае параметра, определяющего тип диаграммы). По умолчанию стандартной диаграммой Excel является обычная гистограмма. Эта особенность Excel может сыграть весьма неоднозначную роль. С одной стороны, вы можете изменить стандартную диаграмму, чтобы создать неY сколько однотипных диаграмм. С другой стороны, нельзя быть на 100% увеY ренным в том, что один и тот же тип диаграммы используется в качестве станY дартного на всех компьютерах, куда может попасть данная рабочая книга. Переопределить стандартное значение параметра можно с помощью кода VBA. Следующая строка кода изменяет цвет заливки области построения диаграммы с серого на белый: ActiveChart.PlotArea.Interior.ColorIndex = xlNone 236 Часть II Автоматизация Excel Совет Чтобы изменить стандартный тип диаграммы с помощью пользовательского ин& терфейса, щелкните на диаграмме правой кнопкой мыши и выберите команду контекстного меню Тип диаграммы (Chart Type). Выберите требуемый тип диа& граммы и щелкните на кнопке Сделать стандартной (Set As Default Chart). Использование объектных переменных для упрощения кода Объектные переменные позволяют упростить код и сделать его более эфY фективным. В частности, использование объектной переменной делает возY можным обращение к диаграмме без активизации последней. Ниже приведен пример создания объектной переменной типа Chart: Dim Cht As Chart Set Cht = Charts.Add Cht.SourceData = Source:=Sheets("Лист4").Range("A1:B5") Объектные переменные будут использоваться на протяжении оставшейся части этой главы. Еще одним преимуществом объектных переменных является поддержка редактором Visual Basic автозаполнения. Чтобы включить автозаполнение, выберите команду меню редактора Visual Basic Tools Options (Сервис Параметры) и установите флажок Auto List Members (Автозаполнение) на вкладке Editor (Редактор), как показано на рис. 10.7. Рис. 10.7. Автозаполнение позволяет редактору Vi& sual Basic автоматически предлагать подходящий способ продолжения ввода программного кода Автозаполнение позволяет редактору Visual Basic автоматически предY лагать подходящий способ продолжения ввода программного кода. Все, Диаграммы Глава 10 237 что требуется от пользователя YYYY это выбрать нужный элемент из списка, как показано на рис. 10.8. Рис. 10.8. Автозаполнение в действии — после ввода выражения Cht.ChartType = редактор Visual Basic предлагает выбрать требуемую константу из списка Внимание Сбой в работе средства автозаполнения может быть вызван наличием ошибки компилирования в программном коде. Чтобы обнаружить ошибку, выберите ко& манду меню редактора Visual Basic Debug Compile VBA Project (Отладка Компилировать проект VBA). Устранение ошибки компилирования должно при& вести к восстановлению работы средства автозаполнения. “Анатомия” диаграммы В этом разделе рассматриваются VBAYэквиваленты различных элементов диаграммы, их свойства и методы. Все элементы диаграмм можно разделить на две категории: элементы, общие для всех диаграмм (например, область диаграммы, область поY строения диаграммы, название диаграммы и легенда), и элементы, харакY терные только для диаграмм определенного типа (например, угол повороY та круговой диаграммы и параметры проекции трехмерных диаграмм). График, точечная диаграмма и диаграмма с областями имеют по две оси данных; лепестковая диаграмма YYYY одну ось для каждой категории данных; круговая и кольцевая диаграммы не имеют осей как таковых. Область диаграммы (ChartArea) Объект ChartArea представляет собой контейнер для всех остальных элеY ментов диаграммы, таких как область построения диаграммы, оси, легенда, ряды данных, подписи данных и т.д. Наиболее распространенными изменеY ниями, вносимыми в область диаграммы, являются определение формата обY ласти диаграммы (выбор границы, цвета заливки и текстуры) и выбор параY метров шрифта. Рассмотрим пример форматирования области диаграммы, для чего запишем небольшой макрос. Щелкните правой кнопкой мыши на области диаграммы и 238 Часть II Автоматизация Excel выберите команду контекстного меню Формат области диаграммы (Format Chart Area). На вкладке Вид (Patterns) диалогового окна Формат области диаграммы (Format Chart Area) выберите светлоYбирюзовый цвет заливки, красный цвет рамки, третью по толщине линию и установите флажок С тенью (Shadow). На вкладке Шрифт (Font) выберите размер шрифта 14 и снимите флажок Автомасштабирование (Auto scale). Ниже приведен код, сгенерированY ный средством записи макроса в результате выполнения указанных действий: Sub Macro2AsRecorded() Sheets("Диаграмма 1").Activate ActiveChart.ChartArea.Select With Selection.Border .ColorIndex = 3 .Weight = xlMedium .LineStyle = xlContinuous End With Selection.Shadow = True With Selection.Interior .ColorIndex = 34 .PatternColorIndex = 1 .Pattern = xlSolid End With Selection.AutoScaleFont = False With Selection.Font .Name = "Arial" .FontStyle = "Regular" .Size = 14 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = xlAutomatic .Background = xlAutomatic End With End Sub Средство записи макросов зафиксировало все действия, выполненные поY средством пользовательского интерфейса (и даже больше). Следующий шаг состоит в оптимизации полученного кода. Строки, набранные полужирным шрифтом, являются избыточными: Sub Macro2AsRecorded() Sheets("Диаграмма 1").Activate ActiveChart.ChartArea.Select With Selection.Border .ColorIndex = 3 .Weight = xlMedium ' Значение по умолчанию. .LineStyle = xlContinuous End With Selection.Shadow = True With Selection.Interior ' Светло-бирюзовый цвет заливки. .ColorIndex = 34 Диаграммы Глава 10 239 ' Значение по умолчанию. .PatternColorIndex = 1 ' Значение по умолчанию. .Pattern = xlSolid End With Selection.AutoScaleFont = False With Selection.Font ' Значение не изменилось. .Name = "Arial" ' Значение не изменилось. .FontStyle = "Regular" .Size = 14 ' Значение не изменилось. .Strikethrough = False ' Значение не изменилось. .Superscript = False ' Значение не изменилось. .Subscript = False ' Значение не изменилось. .OutlineFont = False ' Значение не изменилось. .Shadow = False ' Значение не изменилось. .Underline = xlUnderlineStyleNone ' Значение не изменилось. .ColorIndex = xlAutomatic ' Значение не изменилось. .Background = xlAutomatic End With End Sub Ниже приведен оптимизированный код макроса: Sub Macro2Shortened() Sheets("Диаграмма 1").Activate ActiveChart.ChartArea.Select With Selection.Border .ColorIndex = 3 .Weight = xlMedium End With Selection.Shadow = True With Selection.Interior .ColorIndex = 34 End With Selection.AutoScaleFont = False With Selection.Font .Size = 14 End With End Sub С целью дальнейшего упрощения кода создадим объектную переменную, представляющую область диаграммы. Обратите внимание, что при использоY вании объектной переменной к области диаграммы можно обращаться без ее предварительного выделения. Более того, следующий код будет выполняться корректно даже в том случае, когда лист диаграммы не будет активным: 240 Часть II Автоматизация Excel Sub ChartArDemo() Dim ChtArea As ChartArea Set ChtArea = Charts("Диаграмма 1").ChartArea With ChtArea .Shadow = True With .Border .ColorIndex = 3 .Weight = xlMedium End With .Interior.ColorIndex = 34 .AutoScaleFont = False .Font.Size = 14 End With End Sub Поговорим о цвете При выборе цвета линии, заливки или шрифта Excel предлагает использовать стандартную палитру, состоящую из 56 цветов. Чтобы изменить любой цвет станY дартной палитры, выберите команду меню Excel Сервис Параметры (Tools Options), перейдите во вкладку Цвет (Color) и, указав требуемый цвет, щелкните на кнопке Изменить (Modify). Измененная палитра сохраняется вместе с рабочей книгой. Для доступа к цветам палитры можно использовать свойство рабочей книги Colors. Следующие строки кода полностью эквивалентны: .Border.ColorIndex = 3 .Border.Color = ThisWorkbook.Colors(3) Обратите внимание, что порядок цветов в палитре не соответствует их инY дексу, т.е. значению свойства ColorIndex. Например, в стандартной палитре красный цвет (1Yй столбец 3Yй строки) имеет индекс 3, а бирюзовый (5Yй столбец 4Yй строки) YYYY 8. Наиболее простой способ определения индекса цвеY та заключается в записи простого макроса, устанавливающего требуемый цвет для произвольного элемента интерфейса. Любой цвет на экране может быть получен путем смешивания трех основY ных цветов YYYY красного, зеленого и синего. В VBA есть функция RGB, позвоY ляющая создать практически любой цвет путем указания интенсивности его составляющих. Ниже приведен синтаксис функции RGB: RGB(red, green, blue) Каждый из аргументов принимает значения в диапазоне от 0 до 255. СлеY дующие строки кода полностью эквивалентны и используются для установки красного цвета границы: .Border.ColorIndex = 3 .Border.Color = RGB(255, 0, 0) Область построения диаграммы (PlotArea) Область построения диаграммы содержит визуализированные ряды данY ных, оси и подписи осей. К ней применимы те же операции форматирования, Диаграммы Глава 10 241 что и к области диаграммы. Вдобавок, вы можете изменять размеры области построения диаграммы и ее размещение в пределах области диаграммы с поY мощью свойств Top, Left, Height и Width объекта PlotArea. Изменение размера и размещения объекта Каждый объект рисунка или диаграммы имеет контейнер. К примеру, конY тейнером автофигуры является рабочий лист, на котором она размещена. ПоY добным образом, контейнером области построения диаграммы является область самой диаграммы. Объект, заключенный в контейнер, имеет ограничивающий прямоугольник YYYY наименьший прямоугольник, в который полностью вписываY ется данный объект. За единицу измерения в Excel VBA принята точка, составляющая 1/72 дюйма. Размещение объекта полностью определяется расстоянием по вертикали и горизонтали от верхнего левого угла объекта до верхнего левого угла его контейY нера. Расстоянию по вертикали соответствует свойство объекта Top, а расстояY нию по горизонтали YYYY свойство объекта Left. Высота и ширина объекта совY падают с высотой и шириной его ограничивающего прямоугольника (рис. 10.9). Рис. 10.9. Размещение объекта полностью опреде& ляется расстоянием по вертикали и горизонтали от верхнего левого угла объекта до верхнего левого угла его контейнера. Высота и ширина объекта совпадают с высотой и шириной его ограничи& вающего прямоугольника Свойства Top, Left, Height и Width поддерживают как считывание, так и установку значения. Ниже приведен пример определения высоты объекта: ObjHt = obj.Height и изменения его размещения: obj.Top = 75 obj.Left = 80 Рассмотрим следующий код: Sub PlotArDemo() Dim PltArea As PlotArea 242 Часть II Автоматизация Excel Set PltArea = Charts("Диаграмма 3").PlotArea With PltArea .Top = 100 .Left = 100 .Height = 300 .Width = 400 End With End Sub Результат его выполнения представлен на рис. 10.10. Рис. 10.10. Свойства объекта Top, Left, Height и Width позволяют изменить его размер и размещение Наличие свойств Top, Left, Height и Width позволяет задать размер и размещение объекта с большей точностью, чем это можно было бы сделать с помощью пользовательского интерфейса. Изменяя значения этих свойств, следует помнить об их естественных ограничениях. К примеру, сумма значеY ний свойств объекта Left и Width не может превысить значение свойства Width контейнера объекта. Ряды данных (Series) Ряды данных диаграммы входят в коллекцию SeriesCollection. РасY сматриваемая в качестве примера диаграмма имеет два ряда данных YYYY Xdata и Ydata. Ниже приведен синтаксис обращения к ряду данных: Cht.SeriesCollection(Index) Диаграммы Глава 10 243 Index — это номер (начиная с 1) или имя ряда данных. Щелкнув на точке данных (столбце) из ряда Xdata, вы увидите в строке формул следующее выY ражение: =РЯД(Лист1!$A$1;;Лист1!$A$2:$A$5;1) (В англоязычной версии Excel: =SERIES(Лист1!$A$1,,Лист1!$A$2: $A$5,1).) Лист1!$A$1 YYYY это имя ряда данных (Xdata). По умолчанию второй параметр пропущен, так как ось категорий гистоY граммы содержит последовательность порядковых чисел (начиная с 1), соотY ветствующих точкам данных. При необходимости этот параметр может содерY жать ссылку на диапазон ячеек, определяющий подписи оси категорий. Лист1!$A$2:$A$5 YYYY это диапазон ячеек, в котором находится ряд данных. Наконец, 1 YYYY это индекс ряда данных в коллекции. Чтобы изменить инY декс ряда данных с помощью пользовательского интерфейса, щелкните на точке данных правой кнопкой мыши, выберите команду контекстного меню Формат рядов данных (Format Data Series) и перейдите во вкладку Порядок рядов (Series Order). Ниже приведены два эквивалентных способа обращения к ряду данных Xdata: Charts("Диаграмма 1").SeriesCollection("Xdata") Charts("Диаграмма 1").SeriesCollection(1) Следующий макрос позволяет создать комбинированную диаграмму, в коY торой ряд данных Xdata будет представлен в виде графика: Sub SeriesDemo() Dim Ser As Series Set Ser = Charts("Диаграмма 3").SeriesCollection("Xdata") With Ser .ChartType = xlLine .Border.Weight = xlThick .MarkerStyle = xlMarkerStyleCircle .MarkerBackgroundColorIndex = xlAutomatic .MarkerForegroundColorIndex = xlAutomatic .MarkerSize = 10 End With End Sub Результат выполнения макроса представлен на рис. 10.11. Оси диаграммы (Axis) Оси диаграммы входят в коллекцию Axes. Рассматриваемая в качестве примера диаграмма имеет две оси YYYY ось категорий (X) и ось значений (Y). Ниже приведен сокращенный синтаксис обращения к оси диаграммы: Cht.Axes(Type) Type — это константа Excel VBA, определяющая тип оси. Ось категорий (X) имеет тип xlCategory, а ось значений (Y) YYYY тип xlValue. 244 Часть II Автоматизация Excel Рис. 10.11. Пример создания комбинированной диаграммы путем изменения спосо& ба представления (ChartType) ряда данных Чтобы обратиться ко всей коллекции осей диаграммы, пропустите параY метр Type, как показано ниже: Cht.Axes Следующий макрос добавляет к диаграмме подписи осей и изменяет форY мат данных оси Y (оси значений): Sub AxisDemo() Dim Axs As Axis Set Axs = Charts("Диаграмма 3").Axes(xlValue) With Axs .HasTitle = True .AxisTitle.Caption = "Эффективность производства" .TickLabels.NumberFormat = "0.00" End With Set Axs = Charts("Диаграмма 3").Axes(xlCategory) With Axs .HasTitle = True .AxisTitle.Caption = "Год" End With End Sub Результат выполнения макроса представлен на рис. 10.12. Обратите внимание, что добавление подписей осей привело к автоматичеY скому изменению размера области построения диаграммы. Чтобы задать подY пись диаграммы, необходимо установить значение свойства оси HasTitle равным True. Диаграммы Глава 10 245 Рис. 10.12. Пример добавления к диаграмме подписей осей и изменения формата данных оси значений Добавление вспомогательных осей Если масштаб рядов данных сильно отличается, может возникнуть необхоY димость добавления вспомогательной оси (горизонтальной или вертикальY ной). Ниже приведен полный синтаксис обращения к оси диаграммы: Cht.Axes(Type, AxisGroup) AxisGroup — это константа Excel VBA, определяющая группу оси. ОсновY ные оси входят в группу xlPrimary, а вспомогательные YYYY в группу xlSecondary. Следующий макрос определяет вспомогательную ось значений (Y) для ряда данных Xdata. Sub SecondaryAxisDemo() Dim Cht As Chart Set Cht = Charts("Диаграмма 3") Cht.SeriesCollection("Xdata").AxisGroup = 2 End Sub Результат выполнения макроса представлен на рис. 10.13. Линии сетки (HasMajorGridlines и HasMinorGridlines) Линии сетки являются расширением меток делений оси и предназначены для улучшения восприятия и оценки отображаемых данных. Основным и промежуточным меткам делений оси соответствуют основные и промежуY точные линии сетки, которые можно скрыть или отобразить независимо друг 246 Часть II Автоматизация Excel от друга. Линия сетки имеет настраиваемые параметры, такие как тип, цвет и толщина. Рис. 10.13. Если масштаб рядов данных сильно отличается, добавьте вспомо& гательную ось Следующий макрос удаляет все линии сетки диаграммы: Sub GridlineDemo() Dim Cht As Chart Set Cht = Charts("Диаграмма 3") With Cht With .Axes(xlValue) .HasMajorGridlines = False .HasMinorGridlines = False End With With Cht.Axes(xlValue) .HasMajorGridlines = False .HasMinorGridlines = False End With End With End Sub Подписи данных (DataLabels и DataLabel) Ряд состоит из точек данных. Каждая точка может иметь свою собственную подпись, включающую значение по оси X (категорий), оси Y (значений) или значение, определенное пользователем (строковая константа или ссылка на ячейку). Ниже перечислены различные способы создания подписи данных с помощью VBA. Все точки данных диаграммы имеют подписи одного и того же типа: ActiveChart.ApplyDataLabels Type:=xlDataLabelsShowNone Диаграммы Глава 10 247 Точки данных определенного ряда имеют подписи одного и того же типа: With ActiveChart.SeriesCollection("Xdata") .HasDataLabels = True .ApplyDataLabels Type:=xlDataLabelsShowValue End With Определенная точка в ряде данных имеет подпись в виде строковой константы: With ActiveChart.SeriesCollection("Xdata").Points(1) .HasDataLabel = True .DataLabel.Text="Подпись точки данных" End With Определенная точка в ряде данных имеет подпись в виде R1C1Y формулы (ссылки на ячейку): With ActiveChart.SeriesCollection("Xdata").Points(1) .HasDataLabel = True .DataLabel.Text="=Лист1!R1C1" End With Подпись данных имеет настраиваемые параметры, такие как положение, ориентация и т.п. Следующий макрос создает подписи для точек данных ряда Xdata. Sub DataLabelDemo() With Charts("Диаграмма 3").SeriesCollection("Xdata") .HasDataLabels = True .ApplyDataLabels Type:=xlDataLabelsShowValue With .DataLabels .HorizontalAlignment = xlCenter .VerticalAlignment = xlCenter .Position = xlLabelPositionAbove .Orientation = xlUpward End With End With End Sub Название диаграммы, легенда и таблица данных (ChartTitle, HasLegend и HasDataTable) Название диаграммы и легенда имеют такие настраиваемые параметры, как шрифт и размещение. Кроме того, рядом с каждой диаграммой можно отобразить таблицу ее исходных данных. Следующий макрос добавляет к диаграмме ее название (отформатированное полужирным шрифтом 16Yго размера и размещенное в верхнем левом углу), легенду (расположенную внизу и посередине диаграммы) и таблицу данных. Обратите внимание, что установка параметров названия диаграммы, леY генды и таблицы данных становится возможной только после присвоения значения True свойствам HasTitle, HasLegend и HasDataTable, соответY ственно. Sub DemoMisc() With Charts("Диаграмма 3") 248 Часть II Автоматизация Excel .HasTitle = True With .ChartTitle .Text = "Эффективность работы компании" .Font.Size = 16 .Top = 0 .Left = 0 End With .HasLegend = True .Legend.Position = xlBottom .HasDataTable = True End With End Sub Линии тренда и полосы погрешности (Trendlines и ErrorBar) Линия тренда дает наглядное представление о направлении изменения ряY да данных. Excel содержит несколько типов линий тренда: линейная, логаY рифмическая, полиномиальная, степенная, экспоненциальная и линейная фильтрация. Полосы погрешности позволяют оценить отклонение фактичеY ских данных от тренда. На рис. 10.14 показана точечная диаграмма годовых продаж. Следующий макрос добавляет к диаграмме линию тренда с прогнозом на 5 периодов (лет) вперед, выводит уравнение линии тренда и величину достоY 2 верности аппроксимации (R ): Sub AddTrendLine() Dim Ser As Series Dim Trnd As Trendline Dim Cht As Chart Set Cht = Worksheets("Тренд и погрешности").ChartObjects(" _ Диаграмма 1").Chart Set Ser = Cht.SeriesCollection(1) Set Trnd = Ser.Trendlines.Add(Type:=xlLinear, Forward:=5, _ Backward:=0, DisplayEquation:=True, DisplayRSquared:=True) Trnd.Border.LineStyle = xlDot With Cht .HasTitle = True .ChartTitle.Characters.Text = "Прогнозируемые продажи" End With End Sub Результат выполнения макроса показан на рис. 10.15. Величина достоверY 2 ности аппроксимации (R ), близкая к 1, означает, что линия тренда соответстY вует фактическим данным. Следующий макрос добавляет к диаграмме полосы погрешности по обе стороны точек данных с фиксированным значением величины погрешности, равным 25 единицам. Sub AddErrorBars() Dim Ser As Series Dim Trnd As Trendline Dim Cht As Chart Set Cht = Worksheets("Тренд и погрешности").ChartObjects(" _ Диаграмма 1").Chart Диаграммы Глава 10 249 Set Ser = Cht.SeriesCollection(1) Ser.ErrorBar Direction:=xlY, Include:=xlBoth, _ Type:=xlFixedValue, Amount:=25 End Sub Рис. 10.14. Чтобы добавить линию тренда, щелкните на любой точке данных диаграммы годовых продаж правой кнопкой мыши и выберите команду контекстного меню Добавить линию тренда (Add Trendline) Полосы погрешности могут размещаться по обе стороны точки данных, выше или ниже ее. Погрешность может определяться как фиксированное знаY чение, относительное значение, заданное число стандартных отклонений, стандартная погрешность, а также с помощью пользовательской формулы. Как показано на рис. 10.16, прогнозируемый уровень продаж выходит за рамY ки допустимой погрешности только в 1987 году. 250 Часть II Автоматизация Excel Рис. 10.15. Добавление линии тренда с прогнозом на 5 периодов (лет) вперед Рис. 10.16. Добавление полос погрешности Диаграммы Глава 10 251 Типы диаграмм В состав Excel входит множество встроенных диаграмм различных типов. Типы и виды стандартных диаграмм Excel перечислены в табл. 10.1. Таблица 10.1. Стандартные диаграммы Excel Тип диаграммы Вид диаграммы Константа VBA Гистограмма Обычная гистограмма xlColumnClustered Объемный вариант обычной гистограммы xl3DColumnClustered Гистограмма с накоплением xlColumnStacked Объемный вариант гистограммы с накоплением xl3DColumnStacked Нормированная гистограмма с накоплением xlColumnStacked100 Объемный вариант нормироY ванной гистограммы с накоплеY нием xl3DColumnStacked100 Трехмерная гистограмма xl3DColumn Обычная линейчатая диаграмма xlBarClustered Объемный вариант обычной лиY нейчатой диаграммы xl3DBarClustered Линейчатая диаграмма с накопY лением xlBarStacked Объемный вариант линейчатой диаграммы с накоплением xl3DBarStacked Нормированная линейчатая диаграмма с накоплением xlBarStacked100 Объемный вариант нормироY ванной линейчатой диаграммы с накоплением xl3DBarStacked100 Обычный график xlLine Обычный график с маркерами xlLineMarkers График с накоплением xlLineStacked Линейчатая диаграмма График 252 Часть II Автоматизация Excel Продолжение табл. 10.1 Тип диаграммы Круговая диаграмма Точечная диаграмма Вид диаграммы Константа VBA График с накоплением, на котоY ром отдельные значения помеY чены маркерами xlLineMarkersStacked Нормированный график с накоY плением xlLineStacked100 Нормированный график с накоY плением, на котором отдельные значения помечены маркерами xlLineMarkersStacked100 Трехмерный график xl3DLine Обычная круговая диаграмма xlPie Разрезанная круговая диаграмма xlPieExploded Объемный вариант обычной круговой диаграммы xl3DPie Объемный вариант разрезанной круговой диаграммы xl3DPieExploded Вторичная круговая диаграмма (обычная круговая диаграмма с частью значений, вынесенными во вторую круговую диаграмму) xlPieOfPie Вторичная гистограмма с накопY лением (обычная круговая диаY грамма с частью значений, выY несенными в гистограмму с наY коплением) xlBarOfPie Обычная точечная диаграмма xlXYScatter Точечная диаграмма со значеY ниями, соединенными сглажиY вающими линиями xlXYScatterSmooth Точечная диаграмма со значеY ниями, соединенными сглажиY вающими линиями без маркеров xlXYScatterSmoothNoMarke rs Точечная диаграмма со значеY ниями, соединенными отрезY ками xlXYScatterLines Диаграммы Глава 10 253 Продолжение табл. 10.1 Тип диаграммы Пузырьковая диаграмма Диаграмма с областями Кольцевая диаграмма Лепестковая диаграмма Вид диаграммы Константа VBA Точечная диаграмма со значеY ниями, соединенными отрезкаY ми без маркеров xlXYScatterLinesNoMarkers Обычная пузырьковая диаграмма xlBubble Объемный вариант обычной пуY зырьковой диаграммы xlBubble3DEffect Обычная диаграмма с областями xlArea Объемный вариант обычной диаграммы с областями xl3DArea Диаграмма с областями с накопY лением xlAreaStacked Объемный вариант диаграммы с областями с накоплением xl3DAreaStacked Нормированная диаграмма с обY ластями с накоплением xlAreaStacked100 Объемный вариант нормироY ванной диаграммы с областями с накоплением xl3DAreaStacked100 Обычная кольцевая диаграмма xlDoughnut Разрезанная кольцевая диаY грамма xlDoughnutExploded Обычная лепестковая диаграмма xlRadar Лепестковая диаграмма с маркеY рами, которыми помечены знаY чения данных xlRadarMarkers Заполненная лепестковая диаY грамма xlRadarFilled Поверхностная Обычная поверхностная диаY диаграмма грамма Контурная диаграмма (вид сверY ху на обычную поверхностную диаграмму) xlSurface xlSurfaceTopView 254 Часть II Автоматизация Excel Продолжение табл. 10.1 Тип диаграммы Биржевая диаграмма ЦилиндриY ческая диаY грамма Вид диаграммы Константа VBA Проволочная (прозрачная) поY верхностная диаграмма xlSurfaceWireframe Проволочная (прозрачная) конY турная диаграмма (вид сверху на проволочную (прозрачную) поY верхностную диаграмму) xlSurfaceTopViewWireframe Биржевая диаграмма для набоY ров из трех значений (самый выY сокий курс, самый низкий курс, курс закрытия) xlStockHLC Биржевая диаграмма для набоY ров из четырех значений (объем, самый высокий курс, самый низкий курс, курс закрытия) xlStockVHLC Биржевая диаграмма для набоY ров из четырех значений (курс открытия, самый высокий курс, самый низкий курс, курс закрыY тия) xlStockOHLC Биржевая диаграмма для набоY ров из пяти значений (объем, курс открытия, самый высокий курс, самый низкий курс, курс закрытия) xlStockVOHLC Обычная гистограмма со столбY цами в виде цилиндров xlCylinderColClustered Обычная линейчатая диаграмма со столбцами в виде цилиндров xlCylinderBarClustered Гистограмма с накоплением со столбцами в виде цилиндров xlCylinderColStacked Линейчатая диаграмма с накопY лением со столбцами в виде циY линдров xlCylinderBarStacked Нормированная гистограмма с накоплением со столбцами в виY де цилиндров xlCylinderColStacked100 Диаграммы Глава 10 255 Окончание табл. 10.1 Тип диаграммы Коническая диаграмма Вид диаграммы Константа VBA Нормированная линейчатая диаY грамма с накоплением со столбY цами в виде цилиндров xlCylinderBarStacked100 Трехмерная гистограмма со столбцами в виде цилиндров xlCylinderCol Обычная гистограмма со столбY цами в виде конусов xlConeColClustered Обычная линейчатая диаграмма со столбцами в виде конусов xlConeBarClustered Гистограмма с накоплением со столбцами в виде конусов xlConeColStacked Линейчатая диаграмма с накоплеY нием со столбцами в виде конусов xlConeBarStacked xlConeColStacked100 Нормированная гистограмма с накоплением со столбцами в виде конусов ПирамидальY ная диаграмма Нормированная линейчатая диаY грамма с накоплением со столбY цами в виде конусов xlConeBarStacked100 Трехмерная гистограмма со столбцами в виде конусов xlConeCol Обычная гистограмма со столбY цами в виде пирамид xlPyramidColClustered Обычная линейчатая диаграмма со столбцами в виде пирамид xlPyramidBarClustered Гистограмма с накоплением со столбцами в виде пирамид xlPyramidColStacked Линейчатая диаграмма с накоплеY нием со столбцами в виде пирамид xlPyramidBarStacked Нормированная гистограмма с накоплением со столбцами в виY де пирамид xlPyramidColStacked100 Нормированная линейчатая диаграмма с накоплением со столбцами в виде пирамид xlPyramidBarStacked100 Трехмерная гистограмма со столбцами в виде пирамид xlPyramidCol 256 Часть II Автоматизация Excel Не отчаивайтесь, взглянув на размеры этой таблицы. С практической точY ки зрения цилиндрические, конические и пирамидальные диаграммы аналоY гичны гистограммам, а линейчатые диаграммы YYYY это гистограммы, повернуY тые на 90° по часовой стрелке. В большинстве случаев разные виды диаграмм в пределах одного типа отличаются значениями нескольких параметров. К примеру, единственное отличие обычной точечной диаграммы (xlXYScatter) от точечной диаграмY мы со значениями, соединенными отрезками (xlXYScatterLines), заклюY чается в том, что параметр SeriesCollection(1).Border.LineStyle последней имеет значение xlAutomatic. Параметры трехмерных и круговых диаграмм В этом разделе рассматриваются параметры, применимые только к трехY мерным или круговым диаграммам. Параметры трехмерных диаграмм Все трехмерные диаграммы имеют параметры объемного вида, являющиеY ся свойствами объекта Chart. Elevation. Возвышение, с которого наблюдатель смотрит на диаY грамму. Если значение параметра Elevation равно 0, наблюдатель не видит верхнюю поверхность фигур. Если значение параметра Elevation равно 90, наблюдатель смотрит на диаграмму сверху вниз. Rotation. Этот параметр принимает значения в диапазоне от 0 до 359. При небольшом значении угла поворота наблюдатель смотрит на диаY грамму так, как если бы он находился справа от нее, а при значении угY ла поворота, равном 330YY350, YYYY так, как если бы он находился слева от нее. Чтобы развернуть диаграмму и посмотреть на нее сзади, испольY зуйте значения параметра Rotation в диапазоне от 150 до 210. Perspective. Этот параметр принимает значения в диапазоне от 0 до 100. Установка больших значений параметра Perspective привоY дит к искривлению основания диаграммы. DepthPercent. Глубина диаграммы в процентах от стандартной глубины. HeightPercent. Высота диаграммы в процентах от стандартной высоты. RightAngleAxes. Установка значения этого параметра равным True исключает возможность изменения перспективы диаграммы, что хаY рактерно для линейчатых диаграмм. Диаграммы Глава 10 257 Объекты Walls (стенки) и Floor (основание) доступны только для трехY мерных диаграмм. Эти объекты имеют одинаковые наборы настраиваемых параметров, таких как цвет заливки, тип рамки и т.п. Ниже перечислены параметры ряда данных трехмерной диаграммы. GapWidth. Если ширина зазора равна 0, фигуры, представляющие на диаграмме один ряд данных, соприкасаются друг с другом. Чем выше ширина зазора, тем больше расстояние между фигурами. GapWidth — это свойство объекта ChartGroup, представляющего все ряды данных одинакового типа. Если один ряд данных на диаграмме представлен трехмерными столбцами, а другой YYYY трехмерным графиком, то шириY на зазора между столбцами будет определяться значением свойства GapWidth объекта Columns3DGroup. GapDepth. Если глубина зазора равна 0, фигуры, представляющие на диаграмме соседние ряды данных, соприкасаются друг с другом. Чем выше глубина зазора, тем больше расстояние между фигурами. НеY смотря на то что глубину зазора можно определить посредством диаY логового окна Формат ряда данных (Format Data Series), GapDepth является свойством объекта Chart. ChartDepth. Если значение параметра ChartDepth равно 20, диаY грамма выглядит очень плоской. Чем больше значение этого параметY ра, тем больше глубина диаграммы. ChartDepth является свойством объекта Chart. Следующий макрос оперирует всеми рассмотренными выше параметрами. Sub Format3D() Dim Cht As Chart Set Cht = Worksheets("Трехмерная _ диаграмма").ChartObjects(1).Chart With Cht .Elevation = 30 .Perspective = 25 .Rotation = 30 .RightAngleAxes = False .HeightPercent = 150 .AutoScaling = True .DepthPercent = 280 .GapDepth = 160 End With Cht.Column3DGroup.GapWidth = 0 With Cht.Walls.Fill .TwoColorGradient Style:=msoGradientHorizontal, Variant:=1 .Visible = True .ForeColor.SchemeColor = 2 .BackColor.SchemeColor = 1 End With With Cht.Floor.Fill .PresetGradient Style:=msoGradientHorizontal, _ Variant:=1, PresetGradientType:=msoGradientCalmWater .Visible = True 258 Часть II Автоматизация Excel End With End Sub Результат выполнения макроса показан на рис. 10.17. Рис. 10.17. Пример изменения параметров трехмерной диаграммы Параметры круговых диаграмм Один из недостатков круговых диаграмм состоит в возможности перекрыY вания подписей данных, как показано на рис. 10.18. К счастью, уникальные свойства круговой диаграммы могут помочь в реY шении этой проблемы. Если друг друга перекрывают всего две подписи данных, попробуйте измеY нить угол поворота первой доли круговой диаграммы с помощью свойства FirstSliceAngle. При этом становится очевидным фундаментальный неY достаток создания диаграмм с помощью VBA. В VBA нет метода или свойства, позволяющего судить о внешней привлекательности диаграммы. Выяснить это можно, только взглянув на нее. Следующий макрос поворачивает диаY грамму на 60° по часовой стрелке, в результате чего взаимное расположение подписей данных становится вполне приемлемым. Sub RotateFirstSlice() Dim Cht As Chart Set Cht = Worksheets("Круговая диаграмма").ChartObjects(1).Chart Cht.PieGroups(1).FirstSliceAngle = 60 End Sub Диаграммы Глава 10 259 Рис. 10.18. Подписи данных круговой диаграммы могут перекрывать друг друга Если же друг друга перекрывает много подписей данных, вынесите все доY ли, не превышающие 5%, во вторичную гистограмму с накоплением: Sub CreateBarOfPie() Dim Cht As Chart Dim CG As ChartGroup Set Cht = Worksheets("Круговая диаграмма").ChartObjects(1).Chart Cht.ChartType = xlBarOfPie Set CG = Cht.PieGroups(1) With CG .SplitType = xlSplitByPercentValue ' Доли менее 5%. .SplitValue = 5 ' Зазор между основной и вторичной диаграммой. .GapWidth = 200 ' Размер вторичной диаграммы в % от основной. .SecondPlotSize = 55 End With End Sub Результат выполнения приведенного выше кода показан на рис. 10.19. Совет Линии выносок, показанные на рис. 10.19, автоматически добавляются Excel, если расстояние между подписью данных и соответствующей ей долей на диаграмме превышает некоторую заданную величину. Чтобы запретить добавление линий выно& сок, установите значение свойства диаграммы HasLeaderLines равным False. 260 Часть II Автоматизация Excel Рис. 10.19. Вторичная гистограмма с накоплением используется для выне& сения из круговой диаграммы долей, не превышающих 5%. В большинстве случаев это позволяет избежать перекрывания подписей данных Интерактивные диаграммы Рассмотрим использование VBA для создания интерактивных диаграмм. События диаграмм Одним из недостатков диаграмм является автоматическое изменение внешнего вида диаграммы при обновлении исходных данных. Управлять изY менением внешнего вида диаграммы помогут события. (Более подробно соY бытия рассматривались в главе 8, ‘‘События’’.) Код обработки событий диаY граммы, расположенной на отдельном рабочем листе, помещается в модуль этого листа. Для обработки событий встроенной диаграммы необходимо созY дать модуль класса. Ниже перечислены некоторые из наиболее часто испольY зуемых событий диаграммы: SeriesChange — срабатывает при обновлении ряда данных на диаY грамме; Calculate — срабатывает при изменении исходных данных диаграммы; Activate — срабатывает при активизации диаграммы; Deactivate — срабатывает при деактивизации диаграммы. Диаграммы Глава 10 261 Следующий макрос выполняется при каждом пересчете диаграммы (например, при обновлении ряда данных). Если количество точек в ряде данY ных с индексом 1 больше 5, отрезки, соединяющие точки данных, окрашиваY ются в красный цвет, в противном случае YYYY в синий. Private Sub Chart_Calculate() Dim Ser As Series Set Ser = Me.SeriesCollection(1) If Ser.Points.Count > 5 Then Ser.Border.ColorIndex = 5 Else Ser.Border.ColorIndex = 3 End If End Sub Экспорт диаграммы в файл изображения Экспортировать диаграмму в файл изображения формата GIF не составляY ет никакого труда: Sub SaveChart() Dim Cht As Chart Set Cht = Worksheets("Круговая диаграмма").ChartObjects(1).Chart Cht.Export Filename:=ThisWorkbook.Path & _ Application.PathSeparator & "pie.gif", FilterName:="GIF" End Sub Подобный шаг может иметь следующие мотивы. Необходимость помещения диаграммы на Web/страницу. Создайте диаY грамму, экспортируйте ее в файл и сошлитесь на него в тексте WebY страницы, например, <Img src="MyChart.gif">. Экономия системных ресурсов. Диаграммы занимают много места в оперативной памяти компьютера. Если вам нужно отобразить 100 или больше диаграмм, воспользуйтесь средствами VBA для создания кажY дой отдельной диаграммы, ее сохранения в файле формата GIF и заY грузки полученного файла в рабочую книгу. Ниже приведен код загрузY ки файла изображения в рабочую книгу: ActiveSheet.Pictures.Insert (ThisWorkbook.Path & _ Application.PathSeparator & "pie.gif") Необходимость помещения диаграммы на пользовательскую форму. Единственный способ помещения диаграммы на пользовательскую форму заключается в ее загрузке из файла изображения. Ниже привеY ден код загрузки файла изображения в элемент управления Image: Me.Image1.Picture = LoadPicture(ThisWorkbook.Path & _ Application.PathSeparator & "pie.gif") 262 Часть II Автоматизация Excel Удивительные возможности точечных диаграмм Несмотря на то, что по части построения чертежей Excel существенно усY тупает таким ‘‘монстрам’’, как AutoCAD, вы будете приятно поражены, отY крыв для себя удивительные возможности точечных диаграмм. На рис. 10.20 показан пример использования точечной диаграммы для построения логотипа компании MrExcel Consulting. Рис. 10.20. Для построения логотипа компании MrExcel Consulting с помощью точечной диаграммы понадобилось 15 точек данных Идея использования точечных диаграмм для построения чертежей была доведена до совершенства Малой Сингхом (Mala Singh) из компании XLSoft Consulting (Индия). На рис. 10.21 показан один из его шедевров. Создание нестандартных диаграмм К сожалению, создание нестандартных диаграмм Excel с помощью VBA выходит за рамки этой книги. Все диаграммы, приведенные в этом разделе, были созданы с помощью VBA и используются с разрешения компании XLSoft Consulting. Круговая пузырьковая диаграмма Внешний вид круговой пузырьковой диаграммы представлен на рис. 10.22. Диаграммы Глава 10 263 Рис. 10.21. Этот чертеж на самом деле является точечной диаграммой Excel. На его построение уходит около 25 с Рис. 10.22. Круговая пузырьковая диаграмма Пузырьковая диаграмма похожа на точечную диаграмму с добавлением третьего ряда данных в виде диаметра пузырька. В свою очередь диаграмма, показанная на рис. 10.22, является расширением пузырьковой диаграммы с добавлением четвертого ряда данных, представленного в виде обычной кругоY вой диаграммы. Круговая пузырьковая диаграмма строится с помощью цикла 264 Часть II Автоматизация Excel по всем точкам четвертого ряда данных. Для каждой точки создается скрытая круговая диаграмма, которая затем используется в качестве изображения соY ответствующего пузырька. Диаграмма с точками данных в виде спидометров На рис. 10.23 показана диаграмма с точками данных в виде спидометров. Рис. 10.23. Диаграмма с точками данных в виде спидометров Диаграмма с точками данных в виде спидометров представляет собой изY мененную точечную диаграмму с двумя автофигурами YYYY кругом для установY ки внешнего периметра и циферблатом. Оставшаяся часть диаграммы предY ставлена точкой и подписями данных. Шкала циферблата и цветовые зоны являются полностью настраиваемыми. Несколько размещенных рядом спиY дометров создают эффект приборной доски. Каждый из спидометров, показанных на рис. 10.23, на самом деле является изображением отдельной диаграммы. Макрос, создающий приборную доску, генерирует диаграмму на основе данных таблицы Excel (одной строке данных соответствует одна приборная доска), а затем использует ее для создания стаY тического изображения. Наконец, полученные изображения спидометров упорядочиваются на рабочем листе. Диаграмма кривой предложения Excel не позволяет создавать гистограммы со столбцами разной ширины. В диаграмме, показанной на рис. 10.24, высота столбца определяет стоимость товара, а ширина YYYY предлагаемое количество. Диаграмма кривой предложения представляет собой точечную диаY грамму с помещенными на область построения диаграммы цветными пряY моугольниками, имитирующими столбцы данных. Ширина и размещение прямоугольников подобраны так, чтобы они корректно отражали исходY ные данные диаграммы. Диаграммы Глава 10 265 Рис. 10.24. Диаграмма кривой предложения Иерархическая кольцевая диаграмма Иерархическая кольцевая диаграмма представляет собой комбинацию круговой и кольцевой диаграммы. Отличительная особенность иерархической кольцевой диаграммы заключается в том, что каждый ее уровень хранит инY формацию о пропорции вложенного уровня. Подписи данных содержат знаY чение и, при необходимости, его вклад (в процентах) в соответствующую долю предыдущего уровня (рис. 10.25). Рис. 10.25. Иерархическая кольцевая диаграмма 266 Часть II Автоматизация Excel Следующий шаг Диаграммы являются неотъемлемой частью всех программ для работы с электронными таблицами, поскольку они позволяют получить наглядное представление об исходных данных. Следующая глава посвящена анализу данных с помощью расширенного фильтра. Глава 11 Àíàëèç äàííûõ ñ ïîìîùüþ ðàñøèðåííîãî ôèëüòðà Преимущества VBA перед пользовательским интерфейсом Excel Диалоговое окно Excel Расширенный фильтр (Advanced Filter) наY столько неинтуитивно, что большинY ство пользователей предпочитают вообще не связываться с ним. ВероY ятно, Microsoft полностью изменит интерфейс расширенного фильтра в следующей версии Excel. С другой стороны, работа с расY ширенным фильтром посредством VBA может доставить истинное наY слаждение. Всего одной строки кода достаточно для извлечения подмноY жества строк исходных данных или отбора уникальных значений из заY данного столбца! При рассмотрении расширенных фильтров в этой главе внимание буY дет уделено как первому, так и втоY рому способу их создания. Одна из причин излишней сложноY сти диалогового окна Расширенный фильтр (рис. 11.1) обусловлена налиY чием параметров фильтра. 11 Преимущества VBA перед пользовательским интерфейсом Excel.......................267 Использование расширенного фильтра для отбора уникальных значений из заданного диапазона................. 268 Использование расширенного фильтра с указанием условия отбора данных..............................276 Фильтрация диапазона исходных данных “на месте” ...287 Использование расширенного фильтра для копирования всех записей, удовлетворяющих заданному условию ......................................... 289 Автофильтр ...................................297 Следующий шаг........................... 298 268 Часть II Автоматизация Excel Рис. 11.1. Диалоговое окно Расширенный фильтр излишне сложно в использова& нии. К счастью, у нас есть VBA! Способ обработки исходных данных. Чтобы показать результат фильтраY ции, скрыв ненужные строки, установите переключатель Фильтровать список на месте (Filter the list, inYplace). Чтобы скопировать отфильтY рованные строки в другую область листа, установите переключатель Скопировать результат в другое место (Copy to another location). Условие отбора. Фильтрация с условием позволяет отобрать подмножеY ство строк, а фильтрация без условия YYYY подмножество столбцов исY ходного диапазона. Фильтрация без условия применяется также при отборе только уникальных записей. Отбор только уникальных записей. Установите флажок Только уникальные записи (Unique records only), для того чтобы отобрать только уникальные значения из заданного диапазона. Использование расширенного фильтра для отбора уникальных значений из заданного диапазона Классический пример использования расширенного фильтра заключается в отборе уникальных значений из заданного диапазона. Предположим, что наY звания компанийYзаказчиков расположены в столбце D исходных данных. Общее число записей неизвестно, однако известно, что данные начинаются с ячейки A2 (1Yя строка используется в качестве строки заголовка). Справа от исходных данных на рабочем листе находится пустое пространство. Отбор уникальных значений из заданного столбца с помощью пользовательского интерфейса Установите указатель ячейки в любом месте исходного диапазона и выберите команду меню Данные Фильтр Расширенный фильтр (Data Filter Advanced Анализ данных с помощью расширенного фильтра Глава 11 269 Filter). При первом вызове этой команды Excel автоматически подставляет в поле Исходный диапазон (List range) адрес исходного диапазона данных. При послеY дующих вызовах команды Данные Фильтр Расширенный фильтр Excel подY ставляет в поле Исходный диапазон его предыдущее значение. Установите флажок Только уникальные записи (Unique records only), расY положенный внизу диалогового окна Расширенный фильтр (Advanced Filter). Установите переключатель Скопировать результат в другое место (Copy to another location) и введите $J$1 в поле Поместить результат в диапазон (Copy to). По умолчанию Excel копирует все столбцы исходного диапазона. Чтобы ограничиться только столбцом D, можно сузить до него исходный диапазон данных или скопировать заголовок столбца D в первую строку области вставки результата. Каждый из способов имеет свои недостатки. Сужение диапазона исходных данных до одного столбца Введите в поле Исходный диапазон (List range) адрес диапазона исходных данных, ограниченного столбцом D. В рассматриваемом случае это означает замену адреса $A$1:$H$1127 адресом $D$1:$D$1127, как показано на рис. 11.2. Рис. 11.2. Сузьте диапазон исходных дан& ных до столбца D Недостаток этого метода заключается в том, что Excel запоминает значение поY ля Исходный диапазон и автоматически подставляет его при следующем вызове команды Данные Фильтр Расширенный фильтр (Data Filter Advanced Filter). Если позже вам понадобится отобрать уникальные значения из столбца C, вам придется изменить адрес исходного диапазона. Копирование заголовка столбца исходных данных в первую строку области вставки результатов Не спешите менять адрес исходного диапазона с $A$1:$H$1127 на $D$1:$D$1127. Вместо этого введите в ячейке J1 заголовок столбца D, в данY ном случае YYYY Заказчик (рис. 11.3). 270 Часть II Автоматизация Excel Рис. 11.3. Чтобы не менять адрес исходного диапазона, скопируйте заголовок столбца D в ячейку J1 Обнаружив в первой строке области вставки результатов заголовок столбY ца D, Excel скопирует исходные данные только из этого столбца. Этот способ ограничения результата фильтрации рекомендуется применять при многоY кратном использовании фильтра. Поскольку Excel запоминает значение поля Исходный диапазон (List range) и автоматически подставляет его при слеY дующем вызове команды Данные Фильтр Расширенный фильтр (Data Filter Advanced Filter), вам не придется каждый раз изменять значение исY ходного диапазона. Результат отбора уникальных значений из столбца D показан на рис. 11.4. Рис. 11.4. Отбор уникальных значений из заданного столбца — классический пример использо& вания расширенного фильтра Анализ данных с помощью расширенного фильтра Глава 11 Отбор уникальных значений из заданного столбца с помощью VBA Команде Данные Фильтр Расширенный фильтр (Data Filter Advanced Filter) соответствует метод VBA .AdvancedFilter. Этот метод имеет 3 параметра. Способ обработки исходных данных. Чтобы показать результат фильтраY ции, скрыв ненужные строки, установите значение параметра Action равным xlFilterInPlace. Чтобы скопировать отфильтрованные строY ки в другую область листа, установите значение параметра Action равY ным xlFilterCopy. В последнем случае установите также значение паY раметра CopyToRange, например, CopyToRange:=Range("J1"). Условие отбора. Чтобы задать условие фильтрации, установите значение параметра CriteriaRange, например, CriteriaRange:=Range ("L1:L2"). Для фильтрации без условия не указывайте значение этого параметра. Отбор только уникальных записей. Чтобы отобрать только уникальные значения из заданного диапазона, установите значение параметра Unique равным True. Следующий код находит результат отбора уникальных значений из столбY ца D и помещает его на два столбца правее последнего столбца исходного диаY пазона данных. Sub GetUniqueCustomers() ' Выделить рабочий лист. Worksheets("Данные").Select ' Очистить результат предыдущего выполнения макроса. Range("J1:AZ1").EntireColumn.Delete Dim IRange As Range Dim ORange As Range ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Копирование заголовка столбца D в 1-ю строку 1-го столбца ' области вставки результатов. ' Определение целевого диапазона данных. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. IRange.AdvancedFilter Action:=xlFilterCopy, _ 271 272 Часть II Автоматизация Excel CopyToRange:=ORange, Unique:=True End Sub По умолчанию расширенный фильтр копирует все столбцы исходного диапазона. Чтобы ограничиться только столбцом D, скопируйте его заголовок в первую строку области вставки результата. В первой части кода определяется размер исходного диапазона данных, точнее YYYY последняя строка исходной области и первый столбец области вставки результата. Несмотря на то, что в этом нет прямой необходимости, адрес исходного и целевого диапазона сохраняется в объектных переменных IRange и ORange, соответственно. Макрос GetUniqueCustomers не требует внесения изменений в свой код при добавлении к исходному диапазону данных новых столбцов. Основное предназначение объектных переменных IRange и Orange состоит в повышеY нии читабельности программного кода. Ниже приведен код макроса, не исY пользующего объектные переменные и не обладающего универсальностью: Sub UniqueCustomerRedux() ' Копирование заголовка столбца D в ячейку J1. Range("J1").Value = Range("D1").Value ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. Range("A1").CurrentRegion.AdvancedFilter xlFilterCopy, _ CopyToRange:=Range("J1"), Unique:=True End Sub Результат выполнения обоих макросов одинаков YYYY справа от исходных данных размещается список уникальных значений из столбца D (см. рис. 11.4). Отсортируем полученный список и подсчитаем объем выручки, приходяY щейся на каждого заказчика. Для этого воспользуемся формулой массива, как показано ниже: Sub RevenueByCustomers() Dim IRange As Range Dim ORange As Range ' Выделить рабочий лист. Worksheets("Данные").Select ' Очистить результат предыдущего выполнения макроса. Range("J1:AZ1").EntireColumn.Delete ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Копирование заголовка столбца D в 1-ю строку 1-го столбца ' области вставки результатов. ' Определение целевого диапазона данных. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. Анализ данных с помощью расширенного фильтра Глава 11 273 IRange.AdvancedFilter Action:=xlFilterCopy, _ CopyToRange:=ORange, Unique:=True ' Определение размера списка заказчиков. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка списка заказчиков. Cells(1, NextCol).Resize(LastRow, 1).Sort Key1:=Cells(1, _ NextCol), Order1:=xlAscending, Header:=xlYes ' Подсчет выручки, приходящейся на каждого заказчика, ' с помощью формулы массива. Cells(1, NextCol + 1).Value = "Выручка" Cells(2, NextCol + 1).FormulaArray = "=SUM((R2C4:R" & _ FinalRow & "C4=RC[-1])*R2C6:R" & FinalRow & "C6)" If LastRow > 2 Then Cells(2, NextCol + 1).Copy Cells(3, _ NextCol + 1).Resize(LastRow - 2, 1) End If End Sub Результат выполнения макроса представлен на рис. 11.5. Список заказчиков может служить источником данY ных для списка или комбинированного списка, располоY женного на пользовательской форме. Создадим макрос, позволяющий генерировать отчет о сделках для выбранY ных заказчиков. Добавьте к проекту форму (назовем ее frmReport), разместите на ней список (установите знаY чение свойства списка MultiSelect равным frmMultiSelectMulti) и 4 кнопки YYYY OK, Отмена, Выбрать все и Очистить. Процедура UserForm_Initialize исY пользуется для заполнения списка на форме данными, полученными в результате отбора уникальных значений из столбца D и их последующей сортировки. Private Sub CancelButton_Click() Unload Me End Sub Private Sub cbSubAll_Click() For i = 0 To lbCust.ListCount - 1 Me.lbCust.Selected(i) = True Next i End Sub Private Sub cbSubClear_Click() For i = 0 To lbCust.ListCount - 1 Me.lbCust.Selected(i) = False Next i End Sub Рис. 11.5. Подсчет вы& ручки, приходящейся на каждого заказчика Private Sub OKButton_Click() For i = 0 To lbCust.ListCount - 1 If Me.lbCust.Selected(i) = True Then ' Создание отчета. RunCustReport WhichCust:=Me.lbCust.List(i) 274 Часть II Автоматизация Excel End If Next i Unload Me End Sub Private Sub UserForm_Initialize() Dim IRange As Range Dim ORange As Range ' Выделить рабочий лист. Worksheets("Данные").Select ' Очистить результат предыдущего выполнения макроса. Range("J1:AZ1").EntireColumn.Delete ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Копирование заголовка столбца D в 1-ю строку 1-го столбца ' области вставки результатов. ' Определение целевого диапазона данных. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:="", CopyToRange:=ORange, Unique:=True ' Определение размера списка заказчиков. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка списка заказчиков. Cells(1, NextCol).Resize(LastRow, 1).Sort _ Key1:=Cells(1, NextCol), Order1:=xlAscending, Header:=xlYes With Me.lbCust .RowSource = "" FinalRow = Range("J65536").End(xlUp).Row For Each cell In Cells(2, NextCol).Resize(LastRow - 1, 1) .AddItem cell.Value Next cell End With ' Удаление списка заказчиков. Cells(1, NextCol).Resize(LastRow, 1).Clear End Sub Ниже приведен код вывода формы frmReport на экран: Sub ShowCustForm() frmReport.Show End Sub Как показано на рис. 11.6, список заказчиков поддерживает множественY ный выбор. Анализ данных с помощью расширенного фильтра Глава 11 275 Рис. 11.6. Использование расширенного фильтра — один из наиболее эффективных способов запол& нения списка на форме Отбор уникальных значений из комбинации нескольких столбцов с помощью VBA Чтобы отобрать уникальные значения из комбинации нескольких столбY цов, скопируйте заголовки этих столбцов в первую строку области вставки реY зультата. Ниже приведен пример отбора уникальных значений из комбинации столбцов B и D. Sub UniqueCustomerProduct() Dim IRange As Range Dim ORange As Range ' Выделить рабочий лист. Worksheets("Данные").Select ' Очистить результат предыдущего выполнения макроса. Range("J1:AZ1").EntireColumn.Delete ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Копирование заголовков столбцов B и D во 2-й и 1-й столбец ' 1-й строки области вставки результатов, соответственно. ' Определение целевого диапазона данных. Range("D1").Copy Destination:=Cells(1, NextCol) Range("B1").Copy Destination:=Cells(1, NextCol + 1) 276 Часть II Автоматизация Excel Set ORange = Cells(1, NextCol).Resize(1, 2) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора уникальных ' значений из комбинации столбцов B и D. IRange.AdvancedFilter Action:=xlFilterCopy, _ CopyToRange:=ORange, Unique:=True ' Определение количества уникальных значений. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка полученного результата. Cells(1, NextCol).Resize(LastRow, 2).Sort Key1:=Cells(1, _ NextCol), Order1:=xlAscending, Key2:=Cells(1, NextCol + 1), _ Order2:=xlAscending, Header:=xlYes End Sub Как показано на рис. 11.7, компания BCD LTD приY обрела всего один товар, зато компания CDE INC — целых три. На заметку Отбор уникальных значений из заданного диапазона — единственный пример использования расширенного фильтра без указания критерия фильтрации данных. Использование расширенного фильтра с указанием условия отбора данных Основное предназначение расширенного фильтра заY ключается в фильтрации, т.е. отборе подмножества исY ходных данных. Это достигается путем задания диапазона условий. Диапазон условий всегда включает в себя нескольY ко строк. Первая строка содержит заголовки столбцов исходного диапазона данных, вторая YYYY значения этих Рис. 11.7. Результат от& бора всех уникальных столбцов, удовлетворяющие критерию отбора. На комбинаций значений рис. 11.8 показан диапазон условий J1:J2 и диапазон столбцов B и D вставки результата L1. Анализ данных с помощью расширенного фильтра Глава 11 277 Рис. 11.8. Пример задания параметров расширенного фильтра, сортирующего товары, приобретенные заказчиком CDE INC. Чтобы отобрать товары, приобретенные заказчиком CDE INC., с помоY щью пользовательского интерфейса Excel, выберите команду меню Данные Фильтр Расширенный фильтр (Data Filter Advanced Filter) и заполните поля открывшегося диалогового окна так, как показано на рис. 11.8. Результат отбора представлен на рис. 11.9. Рис. 11.9. Результат применения расширенного фильтра, сортирующего товары, приобретен& ные заказчиком CDE INC. Аналогичных результатов можно достичь с помощью следующего макроса. Sub UniqueProductsOneCustomer() Dim IRange As Range Dim ORange As Range Dim CRange As Range ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Определение столбца, по которому будет проводиться фильтрация. Cells(1, NextCol).Value = Range("D1").Value ' В действительности, значение CDE INC. должно ' вводиться посредством пользовательской формы. 278 Часть II Автоматизация Excel Cells(2, NextCol).Value = "CDE INC." Set CRange = Cells(1, NextCol).Resize(2, 1) ' Определение целевого диапазона данных. ' Копирование заголовка столбца B1 в столбец L1. Range("B1").Copy Destination:=Cells(1, NextCol + 2) Set ORange = Cells(1, NextCol + 2) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора уникальных ' комбинаций товаров и заданного заказчика. IRange.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:= _ CRange, CopyToRange:=ORange, Unique:=True ' Приведенная выше строка может быть записана так: 'IRange.AdvancedFilter xlFilterCopy, CRange, ORange, True ' Определение количества уникальных значений. LastRow = Cells(65536, NextCol + 2).End(xlUp).Row ' Сортировка полученного результата. Cells(1, NextCol + 2).Resize(LastRow, 1).Sort Key1:=Cells(1, _ NextCol + 2), Order1:=xlAscending, Header:=xlYes End Sub Объединение нескольких условий с помощью логической операции “ИЛИ” Расширенный фильтр позволяет отбирать значения, удовлетворяющие одY ному из двух условий, с помощью логической операции ИЛИ. Примером поY добного объединения условий является отбор заказчиков, которые приобрели товар ABC или товар XYZ. Чтобы объединить условия с помощью операции ‘‘ИЛИ’’, разместите их в последовательных строках диапазона условий, как показано на рис. 11.10. Объединение нескольких условий с помощью логической операции “И” Расширенный фильтр позволяет отбирать значения, удовлетворяющие одY новременно двум условиям, с помощью логической операции И. Примером подобного объединения условий является отбор заказчиков, которые приобY рели товар XYZ в западном регионе. Чтобы объединить условия с помощью операции ‘‘И’’, разместите их в одY ной строке диапазона условий, как показано на рис. 11.11. Анализ данных с помощью расширенного фильтра Рис. 11.10. Диапазон условий J1:J3 ис& пользуется для отбора заказчиков, ко& торые приобрели товар ABC или XYZ Глава 11 279 Рис. 11.11. Диапазон условий J1:K2 использу& ется для отбора заказчиков, которые приоб& рели товар XYZ в западном регионе Дополнительные аспекты объединения условий с помощью логической операции “ИЛИ” Диапазон условий, показанный на рис. 11.12, основан на значении двух различных полей, объединенных с помощью логической операции ‘‘ИЛИ’’. Рис. 11.12. Диапазон условий J1:K3 использует& ся для отбора заказчиков из западного региона и заказчиков, которые приобрели товар XYZ В результате применения расширенного фильтра будут отобраны заказчиY ки из западного региона и заказчики, которые приобрели товар XYZ. Задание условия отбора с помощью формулы Диапазон условий может состоять из множества критериев, объединенных с помощью логических операций. Неэффективность такого подхода становитY ся все более очевидной при увеличении числа критериев. Однако Excel позвоY ляет задавать условие отбора с помощью формулы. Практикум Задание сложного условия отбора Создадим усовершенствованный вариант формы создания отчета на базе формы frmReport. Новая форма позволяет создавать отчет о сделках для выбранного заказчика, товара, региона или их комбинации, как показано на рис. 11.13. Предположим, что пользователь выбрал двух заказчиков и два товара. Соответст& вующий диапазон условий состоит из 5 строк, что вполне приемлемо (рис. 11.14). А теперь представьте, что на некотором диапазоне исходных данных пользова& тель выбрал 10 товаров, 9 регионов и 499 заказчиков. Поскольку диапазон ус& ловий должен содержать все возможные комбинации значений полей, по кото& рым проводится отбор, его размер превысит 44 000 строк. Попробуйте создать 280 Часть II Автоматизация Excel подобный фильтр, и вы вскоре поймете, что на его применение может уйти це& лая вечность. Чтобы не ждать так долго, задайте условие отбора с помощью формулы. Рис. 11.13. Создание диапазона условий для такой формы может превратить& ся в настоящий кошмар Рис. 11.14. Диапазон условий J1:K5 исполь& зуется для отбора заказчиков, которые при& обрели либо товар DEF, либо товар XYZ Использование формул в качестве условия отбора расширенного фильтра Существует альтернативная форма диапазона условий, в соответствии с которой его первая строка (строка заголовка) остается пустой, а во второй строке размещается булева формула. Если последняя содержит относительные ссылки на вторую строку диапазона исходных данных, Excel автоматически применяет формулу ко всем строкам диапазона. Анализ данных с помощью расширенного фильтра Глава 11 281 Рассмотрим задачу отбора всех записей, для которых процент валовой приY были не превышает 53%. Оставим ячейку J1 пустой, а в ячейку J2 поместим булеву формулу =(H2/F2)<0.53. Диапазон условий расширенного фильтра при этом нужно задать как J1:J2. Применяя фильтр, Excel вычислит формулу для каждой строки исходного диаY пазона и отберет те из них, для которых значение формулы будет равным True. Использование формулы в качестве условия отбора расширенного фильтра чрезвычайно эффективно. К тому же, формулы можно объединять с помощью логических операций ‘‘И’’ и ‘‘ИЛИ’’ подобно объединению обычных условий отбора. Практикум Задание диапазона условий на основе формул с помощью пользовательского интерфейса Продемонстрируем создание диапазона условий на основе формул для задачи, рассмотренной в предыдущем практикуме. Скопируйте несколько названий фирм&заказчиков в столбец, расположенный справа от диапазона условий (например, в столбец O). Выделите полученный спи& сок и присвойте ему имя (например, MyCust). Введите в ячейку J2, принадлежа& щую диапазону условий, следующую формулу: =НЕ(ЕНД(ПОИСКПОЗ(D2;MyCust;ЛОЖЬ))) (В англоязычной версии Excel следует использовать формулу =NOT(ISNA(Match (D2,MyCust,False))).) Скопируйте несколько названий товаров в столбец, расположенный справа от диапазона MyCust (например, в столбец P). Выделите полученный список и при& свойте ему имя (например, MyProd). Введите в ячейку K2, принадлежащую диа& пазону условий, следующую формулу: =НЕ(ЕНД(ПОИСКПОЗ(B2;MyProd;ЛОЖЬ))) (В англоязычной версии Excel следует использовать формулу =NOT(ISNA(Match (B2,MyProd,False))).) Наконец, скопируйте несколько названий регионов в столбец, расположенный справа от диапазона MyProd (например, в столбец Q). Выделите полученный спи& сок и присвойте ему имя (например, MyRegion). Введите в ячейку L2, принадле& жащую диапазону условий, следующую формулу: =НЕ(ЕНД(ПОИСКПОЗ(A2;MyRegion;ЛОЖЬ))) (В англоязычной версии Excel следует использовать формулу =NOT(ISNA(Match (A2,MyRegion,False))).) Задав диапазон условий расширенного фильтра как J1:L2, вы сможете отобрать строки исходного диапазона, соответствующие любой комбинации значений диа& пазонов MyCust, MyProd и MyRegion. 282 Часть II Автоматизация Excel Задание диапазона условий на основе формул с помощью VBA Ниже приведен код усовершенствованного варианта формы для создания отчета. Обратите внимание на метод OKButton_Click, создающий диапазон условий на основе формул. Private Sub CancelButton_Click() Unload Me End Sub Private Sub cbSubAll_Click() ' Выделить всех заказчиков. For i = 0 To lbCust.ListCount - 1 Me.lbCust.Selected(i) = True Next i End Sub Private Sub cbSubClear_Click() ' Отменить выделение заказчиков. For i = 0 To lbCust.ListCount - 1 Me.lbCust.Selected(i) = False Next i End Sub Private Sub CommandButton1_Click() ' Отменить выделение товаров. For i = 0 To lbProduct.ListCount - 1 Me.lbProduct.Selected(i) = False Next i End Sub Private Sub CommandButton2_Click() ' Выделить все товары. For i = 0 To lbProduct.ListCount - 1 Me.lbProduct.Selected(i) = True Next i End Sub Private Sub CommandButton3_Click() ' Отменить выделение регионов. For i = 0 To lbRegion.ListCount - 1 Me.lbRegion.Selected(i) = False Next i End Sub Private Sub CommandButton4_Click() ' Выделить все регионы. For i = 0 To lbRegion.ListCount - 1 Me.lbRegion.Selected(i) = True Next i End Sub Private Sub OKButton_Click() Dim CRange As Range, IRange As Range, ORange As Range ' Создание сложного условия, состоящего из нескольких ' условий, объединенных с помощью логического И. NextCCol = 10 NextTCol = 15 Анализ данных с помощью расширенного фильтра Глава 11 283 For j = 1 To 3 Select Case j Case 1 MyControl = "lbCust" MyColumn = 4 Case 2 MyControl = "lbProduct" MyColumn = 2 Case 3 MyControl = "lbRegion" MyColumn = 1 End Select NextRow = 2 ' Проверка выбора пользователя. For i = 0 To Me.Controls(MyControl).ListCount - 1 If Me.Controls(MyControl).Selected(i) = True Then Cells(NextRow, NextTCol).Value = _ Me.Controls(MyControl).List(i) NextRow = NextRow + 1 End If Next i ' Создание новой формулы условия. If NextRow > 2 Then ' Использование относительных ссылок на строку R2 обязательно. ' В англоязычной версии Excel: ' MyFormula = "=NOT(ISNA(MATCH(RC" & MyColumn & ",R2C" & _ ' NextTCol & ":R" & NextRow - 1 & "C" & NextTCol & ",False)))" ' Cells(2, NextCCol).FormulaR1C1 = MyFormula MyFormula = "=НЕ(ЕНД(ПОИСКПОЗ(RC" & _ MyColumn & ";R2C" & NextTCol & ":R" & NextRow - 1 & "C" & _ NextTCol & ";Ложь)))" Cells(2, NextCCol).FormulaR1C1Local = MyFormula NextTCol = NextTCol + 1 NextCCol = NextCCol + 1 End If Next j Unload Me ' На рис. 11.15 показано текущее содержимое рабочего листа. ' Закрыть форму и создать расширенный фильтр с диапазоном ' условий на основе построенных выше формул. If NextCCol > 10 Then Set CRange = Range(Cells(1, 10), Cells(2, NextCCol - 1)) Set IRange = Range("A1").CurrentRegion Set ORange = Cells(1, 20) IRange.AdvancedFilter xlFilterCopy, CRange, ORange ' Очистить диапазон условий. Cells(1, 10).Resize(1, 10).EntireColumn.Clear End If ' Вывести сообщение. MsgBox "Область вставки результата применения фильтра _ начинается с ячейки T1" End Sub 284 Часть II Автоматизация Excel Private Sub UserForm_Initialize() Dim IRange As Range Dim ORange As Range ' Определение размера диапазона исходных данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Определение целевого диапазона данных. ' Копирование заголовка столбца D1 в столбец J1. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:="", CopyToRange:=ORange, Unique:=True ' Определение размера списка заказчиков. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка списка заказчиков. Cells(1, NextCol).Resize(LastRow, 1).Sort Key1:=Cells(1, _ NextCol), Order1:=xlAscending, Header:=xlYes With Me.lbCust .RowSource = "" FinalRow = Range("J65536").End(xlUp).Row For Each cell In Cells(2, NextCol).Resize(LastRow - 1, 1) .AddItem cell.Value Next cell End With ' Удаление списка заказчиков. Cells(1, NextCol).Resize(LastRow, 1).Clear ' Определение целевого диапазона данных. ' Копирование заголовка столбца B1 в столбец J1. Range("B1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца B. IRange.AdvancedFilter Action:=xlFilterCopy, _ CopyToRange:=ORange, Unique:=True ' Определение размера списка товаров. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка списка товаров. Cells(1, NextCol).Resize(LastRow, 1).Sort Key1:=Cells(1, _ NextCol), Order1:=xlAscending, Header:=xlYes With Me.lbProduct .RowSource = "" Анализ данных с помощью расширенного фильтра Глава 11 285 FinalRow = Range("J65536").End(xlUp).Row For Each cell In Cells(2, NextCol).Resize(LastRow - 1, 1) .AddItem cell.Value Next cell End With ' Удаление списка товаров. Cells(1, NextCol).Resize(LastRow, 1).Clear ' Определение целевого диапазона данных. ' Копирование заголовка столбца A1 в столбец J1. Range("A1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца A. IRange.AdvancedFilter Action:=xlFilterCopy, _ CopyToRange:=ORange, Unique:=True ' Определение размера списка регионов. LastRow = Cells(65536, NextCol).End(xlUp).Row ' Сортировка списка регионов. Cells(1, NextCol).Resize(LastRow, 1).Sort Key1:=Cells(1, _ NextCol), Order1:=xlAscending, Header:=xlYes With Me.lbRegion .RowSource = "" FinalRow = Range("J65536").End(xlUp).Row For Each cell In Cells(2, NextCol).Resize(LastRow - 1, 1) .AddItem cell.Value Next cell End With ' Удаление списка регионов. Cells(1, NextCol).Resize(LastRow, 1).Clear End Sub На рис. 11.15 показано содержимое рабочего листа перед выполнением меY тода AdvancedFilter. Макрос помещает выбранные пользователем данные (заказчиков, товары и регионы) в столбцы O, P и Q, а затем определяет диапазон условий как J1:L2. Формула в ячейке J2 проверяет, входит ли значение в ячейке $D2 в список заY казчиков в столбце O. Формулы в ячейках K2 и L2 осуществляют аналогичную проверку для ячеек $B2, $A2 и столбцов P, Q, соответственно. Внимание В справочной системе Excel VBA сказано, что для отбора данных без применения условия достаточно не задать диапазон условий. В Excel 2003 это не так — если вы не определите диапазон условий, метод AdvancedFilter будет использовать значение CriteriaRange, заданное при предыдущем вызове этого метода. Что& бы избежать недоразумений, очистите значение CriteriaRange, например, укажите CriteriaRange="" при вызове метода AdvancedFilter. 286 Часть II Автоматизация Excel Рис. 11.15. Содержимое рабочего листа перед применением расширенного фильтра Использование условия на основе формулы при решении экономических задач Следует признать, что задание диапазона условий расширенного фильтра с помощью формулы YYYY эффективное, но редко используемое решение. В свете этого необходимо упомянуть об одном его весьма интересном приY менении. Ниже приведена формула, позволяющая отобрать строки, значеY ние в столбце A которых больше среднего значения по этому столбцу на всем диапазоне исходных данных: =$A2>СРЗНАЧ($A$2:$A$60000) (В англоязычной версии Excel следует использовать формулу =$A2>AVERAGE( $A$2:$A$60000).) Отбор пустого множества записей Условие расширенного фильтра может быть задано таким образом, что в результате применения последнего будет отобрано пустое множество записей. Чтобы определить данную ситуацию, достаточно найти номер последней строки области вставки результата YYYY если он равен 1 (другими словами, обY ласть вставки результата содержит только строку заголовков столбцов), сообY щите пользователю о том, что его запрос оказался безуспешным и выйдите из процедуры. Анализ данных с помощью расширенного фильтра Глава 11 287 Фильтрация диапазона исходных данных “на месте” При фильтрации диапазона исходных данных ‘‘на месте’’ нет нужды укаY зывать область вставки результата применения фильтра. А вот задание диапаY зона условий является обязательным YYYY в противном случае фильтр отберет 100% исходных строк. Обычно фильтрация ‘‘на месте’’ выполняется с помощью пользовательY ского интерфейса Excel. Чтобы выделить строки, отобранные в результате фильтрации ‘‘на месте’’, необходимо использовать метод VBA SpecialCells с параметром xlCellTypeVisible. Аналогичное действие в пользовательY ском интерфейсе Excel заключается в выборе команды Правка Перейти (Edit Go To), щелчке на кнопке Выделить (Special) в диалоговом окне Переход (Go To) и установке переключателя Только видимые ячейки (Visible cells only) в диалоговом окне Выделение группы ячеек (Go To Special) (рис. 11.16). Рис. 11.16. Фильтрация “на месте” позволяет скрыть строки, не удовлетворяю& щие заданному условию. Чтобы выделить строки, отобранные в результате фильтрации “на месте”, необходимо использовать метод VBA SpecialCells с параметром xlCellTypeVisible Чтобы применить фильтр ‘‘на месте’’, вызовите метод AdvancedFilter, установив значение параметра Action равным xlFilterInPlace и опустив параметр CopyToRange, как показано ниже: IRange.AdvancedFilter Action:=xlFilterInPlace, _ CriteriaRange:=CRange, Unique:=False 288 Часть II Автоматизация Excel Следующий код подсчитывает количество видимых строк в исходном диаY пазоне данных после применения фильтрации ‘‘на месте’’: For Each cell In Range("A2:A" & FinalRow).SpecialCells( _ xlCellTypeVisible) Ctr = Ctr + 1 Next cell MsgBox Ctr & " строк удовлетворяют заданному критерию" Отбор пустого множества записей Условие расширенного фильтра может быть задано таким образом, что в реY зультате применения последнего будет отобрано пустое множество записей. Чтобы определить данную ситуацию при фильтрации ‘‘на месте’’, следует проY верить, возвращает ли метод SpecialCells ошибку времени выполнения 1004 (не найдено ни одной ячейки, удовлетворяющей заданным условиям). Для этого воспользуемся универсальной ловушкой ошибок, как показано ниже. (Более подробно обработка ошибок рассматривается в главе 23, ‘‘Обработка ошибок’’.) On Error GoTo NoRecs For Each cell In Range("A2:A" & FinalRow).SpecialCells( _ xlCellTypeVisible) Ctr = Ctr + 1 Next cell On Error GoTo 0 MsgBox Ctr & " строк удовлетворяют заданному критерию" Range("A1").Select Exit Sub NoRecs: MsgBox "Нет строк, удовлетворяющих заданному критерию" Чтобы подобная ловушка сработала, следует исключить строку заголовка из диапазона ячеек, передаваемого методу SpecialCells. В противном слуY чае метод SpecialCells не сгенерирует ошибку 1004, поскольку строка заY головка остается видимой и после применения расширенного фильтра. Отображение записей, скрытых в результате фильтрации “на месте” Чтобы отобразить все строки исходного диапазона, скрытые в результате применения расширенного фильтра ‘‘на месте’’, воспользуйтесь методом ShowAllData, как показано ниже: ActiveSheet.ShowAllData Отбор только уникальных записей при фильтрации “на месте” В результате отбора только уникальных записей при фильтрации ‘‘на месY те’’ расширенный фильтр скроет строки, в которых одинаковыми являются Анализ данных с помощью расширенного фильтра Глава 11 289 значения всех столбцов исходного диапазона. Другими словами, фильтрация ‘‘на месте’’ не позволяет отобрать строки с уникальной комбинацией только некоторого подмножества столбцов исходного диапазона, например, столбца с названием фирмыYзаказчика и столбца с наименованием товара. Использование расширенного фильтра для копирования всех записей, удовлетворяющих заданному условию Ранее в этой главе рассматривался отбор уникальных значений из исходY ного диапазона данных и их копирование в другую область рабочего листа. В частности, составлялись списки заказчиков, регионов и товаров, которые затем использовались при заполнении соответствующих списков на форме. Тем не менее, в повседневной жизни расширенный фильтр обычно исY пользуется для отбора всех записей, удовлетворяющих определенному услоY вию. Например, при генерации отчета о сделках фильтр возвращает все запиY си, соответствующие выбранным пользователем заказчикам. Чтобы отобрать все записи, удовлетворяющие заданному критерию, сбросьте флажок Только уникальные записи (Unique records only) в диалогоY вом окне Расширенный фильтр (Advanced Filter) (если расширенный фильтр создается с помощью пользовательского интерфейса Excel) или установите значение параметра Unique метода AdvancedFilter равным False (если расширенный фильтр создается с помощью VBA). Если требуется отобрать только подмножество столбцов исходного диапаY зона данных, скопируйте их заголовки в первую строку области вставки реY зультата фильтрации, при необходимости изменив порядок их следования. В следующих разделах рассматриваются различные примеры использоваY ния расширенного фильтра. Копирование всех столбцов исходного диапазона данных Чтобы скопировать все столбцы строк, удовлетворяющих заданному услоY вию, укажите в качестве области вставки результата пустую ячейку. Sub AllColumnsOneCustomer() Dim IRange As Range Dim ORange As Range Dim CRange As Range ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Определение столбца, по которому будет проводиться фильтрация. Cells(1, NextCol).Value = Range("D1").Value ' В действительности, значение CDE INC. должно ' вводиться посредством пользовательской формы. 290 Часть II Автоматизация Excel Cells(2, NextCol).Value = "CDE INC." Set CRange = Cells(1, NextCol).Resize(2, 1) ' Определение целевого диапазона данных (пустая ячейка). Set ORange = Cells(1, NextCol + 2) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора строк, ' удовлетворяющих заданному условию. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=CRange, CopyToRange:=ORange Range("L1").Select End Sub Результат выполнения приведенного выше макроса показан на рис. 11.17. Рис. 11.17. Указав пустую ячейку в качестве области вставки результата приме& нения расширенного фильтра, вы получите все столбцы строк, удовлетворяю& щих заданному условию Копирование и переупорядочивание подмножества столбцов исходного диапазона данных Применяя расширенный фильтр для создания отчета, вы, вероятно, захоY тите включить в последний только некоторые столбцы исходного диапазона данных. Возвратимся к форме frmReport, рассматривавшейся ранее в этой главе. Форма frmReport предназначается для создания отчета о сделках для выY бранных пользователем заказчиков. Создание отчета осуществляется с помоY щью процедуры RunCustReport, которая принимает в качестве параметра имя заказчика. Предположим, что по определенным соображениям в отчет нужно включить только столбцы Дата, Количество, Товар и Выручка (в укаY занном порядке). Следующий код копирует соответствующие заголовки столбцов в первую строку области вставки результата применения расширенного фильтра. Метод AdvancedFilter отбирает строки, удовлетворяющие заданному условию, как показано на рис. 11.18. Анализ данных с помощью расширенного фильтра Глава 11 291 Рис. 11.18. Содержимое рабочего листа после применения расширенного фильтра После этого процедура RunCustReport копирует отобранные строки в новую рабочую книгу, добавляет заголовок отчета, итоговую строку и сохраY няет рабочую книгу в файле с именем, совпадающим с названием соответстY вующей фирмыYзаказчика. Пример отчета о сделках для заказчика CDE INC. показан на рис. 11.19. Рис. 11.19. Отчет о сделках для заказчика CDE INC. Sub RunCustReport(WhichCust As Variant) Dim IRange As Range Dim ORange As Range Dim CRange As Range Dim WBN As Workbook Dim WSN As Worksheet Dim WSO As Worksheet Set WSO = ActiveSheet ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' Определение условия отбора. Cells(1, NextCol).Value = Range("D1").Value Cells(2, NextCol).Value = WhichCust Set CRange = Cells(1, NextCol).Resize(2, 1) 292 Часть II Автоматизация Excel ' Определение целевого диапазона данных. ' В целевой диапазон войдут столбцы C (Дата), ' E (Количество), B (Товар) и F (Выручка). Cells(1, NextCol + 2).Resize(1, 4).Value = Array(Cells(1, _ 3), Cells(1, 5), Cells(1, 2), Cells(1, 6)) Set ORange = Cells(1, NextCol + 2).Resize(1, 4) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора строк, ' удовлетворяющих заданному условию. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=CRange, CopyToRange:=ORange ' Содержимое рабочего листа на текущий ' момент показано на рис. 11.18. ' Создание новой рабочей книги для размещения ' результата применения расширенного фильтра. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSN = WBN.Worksheets(1) ' Определение заголовка отчета. WSN.Cells(1, 1).Value = "Отчет о сделках для заказчика " _ & WhichCust ' Копирование данных с текущего активного ' рабочего листа в новую рабочую книгу. WSO.Cells(1, NextCol + 2).CurrentRegion.Copy _ Destination:=WSN.Cells(3, 1) TotalRow = WSN.Cells(65536, 1).End(xlUp).Row + 1 WSN.Cells(TotalRow, 1).Value = "Всего" ' В англоязычной версии Excel: ' WSN.Cells(TotalRow, 2).FormulaR1C1 = "=SUM(R2C:R[-1]C)" ' WSN.Cells(TotalRow, 4).FormulaR1C1 = "=SUM(R2C:R[-1]C)" WSN.Cells(TotalRow, 2).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" WSN.Cells(TotalRow, 4).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" ' Стилевое форматирование отчета. WSN.Cells(3, 1).Resize(1, 4).Font.Bold = True WSN.Cells(TotalRow, 1).Resize(1, 4).Font.Bold = True WSN.Cells(1, 1).Font.Size = 18 WBN.SaveAs "C:\" & WhichCust & ".xls" WBN.Close SaveChanges:=False WSO.Select ' Очистить область вставки результата ' применения расширенного фильтра. Range("J1:Z1").EntireColumn.Clear End Sub Анализ данных с помощью расширенного фильтра Глава 11 293 Процедура RunCustReport — это простой, однако весьма эффективный способ создания отчетов, который может применить на практике любой польY зователь Excel. Практикум Использование двух расширенных фильтров для создания отчетов по каждому заказчику Рассмотрим итоговый макрос, применяющий два расширенных фильтра различ& ного типа для создания отчетов по каждому заказчику. 1. Первый расширенный фильтр используется для создания списка заказчиков в столбце J. Параметр Unique метода AdvancedFilter имеет значение True, а параметр CopyToRange содержит ссылку на ячейку J1, содержащую заголо& вок столбца D. ' Первый расширенный фильтр - создание ' списка заказчиков в столбце J. ' Определение целевого диапазона. ' Копирование заголовка столбца D в ячейку J1. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. IRange.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:="", _ CopyToRange:=ORange, Unique:=True 2. Для каждого заказчика из списка выполняются действия, описанные в пп. 3–7. Приведенный ниже код определяет размер списка заказчиков и реализует со& ответствующий цикл. ' Цикл по списку заказчиков. FinalCust = Cells(65536, NextCol).End(xlUp).Row For Each cell In Cells(2, NextCol).Resize(FinalCust - 1, 1) ThisCust = cell.Value ' Выполнение действий, описанных в пп. 3-7. Next Cell 3. Условие отбора второго расширенного фильтра содержится в ячейках L1:L2 (заголовок столбца D в ячейке L1, имя заказчика — в ячейке L2). ' Определение условия отбора. Cells(1, NextCol + 2).Value = Range("D1").Value Cells(2, NextCol + 2).Value = ThisCust Set CRange = Cells(1, NextCol + 2).Resize(2, 1) 4. Второй расширенный фильтр используется для копирования строк, удовлетво& ряющих заданному условию, в область, начинающуюся со столбца N. Параметр Unique метода AdvancedFilter имеет значение False, а параметр CopyToRange представляет собой ссылку на диапазон ячеек N1:Q1, содержащий заго& ловки необходимых столбцов исходного диапазона данных. 294 Часть II Автоматизация Excel ' Определение целевого диапазона данных. ' В целевой диапазон войдут столбцы C (Дата), ' E (Количество), B (Товар) и F (Выручка). Cells(1, NextCol + 4).Resize(1, 4).Value = Array(Cells(1, 3), _ Cells(1, 5), Cells(1, 2), Cells(1, 6)) Set ORange = Cells(1, NextCol + 4).Resize(1, 4) ' Второй расширенный фильтр - отбор строк, ' удовлетворяющих заданному условию. IRange.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=CRange, CopyToRange:=ORange 5. Результат применения второго расширенного фильтра копируется в новую ра& бочую книгу. Для создания рабочей книги используется метод Workbooks.Add. ' Создание новой рабочей книги для размещения ' результата применения расширенного фильтра. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSN = WBN.Worksheets(1) ' Определение заголовка отчета. WSN.Cells(1, 1).Value = "Отчет о сделках заказчика " & ThisCust ' Копирование данных с текущего активного ' рабочего листа в новую рабочую книгу. WSO.Cells(1, NextCol + 4).CurrentRegion.Copy _ Destination:=WSN.Cells(3, 1) 6. Последний штрих — добавление заголовка отчета и итоговой строки. Вдобавок, строка заголовков столбцов и итоговая строка выделяются полужирным шрифтом. ' Определение заголовка отчета. WSN.Cells(1, 1).Value = "Отчет о сделках заказчика " & ThisCust TotalRow = WSN.Cells(65536, 1).End(xlUp).Row + 1 WSN.Cells(TotalRow, 1).Value = "Всего" ' В англоязычной версии Excel: ' WSN.Cells(TotalRow, 2).FormulaR1C1 = "=SUM(R2C:R[-1]C)" ' WSN.Cells(TotalRow, 4).FormulaR1C1 = "=SUM(R2C:R[-1]C)" WSN.Cells(TotalRow, 2).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" WSN.Cells(TotalRow, 4).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" ' Стилевое форматирование отчета. WSN.Cells(3, 1).Resize(1, 4).Font.Bold = True WSN.Cells(TotalRow, 1).Resize(1, 4).Font.Bold = True WSN.Cells(1, 1).Font.Size = 18 7. Новая рабочая книга сохраняется в файле с именем, совпадающим с названием соответствующей фирмы&заказчика, после чего эта книга закрывается. Перед переходом к следующей итерации цикла макрос очищает область вставки ре& зультата выполнения обоих расширенных фильтров. WBN.SaveAs "C:\" & ThisCust & ".xls" WBN.Close SaveChanges:=False WSO.Select Set WSN = Nothing Set WBN = Nothing Анализ данных с помощью расширенного фильтра Глава 11 295 ' Очистить область вставки результата ' применения расширенных фильтров. Cells(1, NextCol + 2).Resize(1, 10).EntireColumn.Clear Ниже приведен полный код макроса RunReportForEachCustomer. Sub RunReportForEachCustomer() Dim IRange As Range Dim ORange As Range Dim CRange As Range Dim WBN As Workbook Dim WSN As Worksheet Dim WSO As Worksheet Set WSO = ActiveSheet ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' ' ' ' Первый расширенный фильтр - создание списка заказчиков в столбце J. Определение целевого диапазона. Копирование заголовка столбца D в ячейку J1. Range("D1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца D. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:="", CopyToRange:=ORange, Unique:=True FinalCust = Cells(65536, NextCol).End(xlUp).Row ' Цикл по списку заказчиков. For Each cell In Cells(2, NextCol).Resize(FinalCust - 1, 1) ThisCust = cell.Value ' Определение условия отбора. Cells(1, NextCol + 2).Value = Range("D1").Value Cells(2, NextCol + 2).Value = ThisCust Set CRange = Cells(1, NextCol + 2).Resize(2, 1) ' Определение целевого диапазона данных. ' В целевой диапазон войдут столбцы C (Дата), ' E (Количество), B (Товар) и F (Выручка). Cells(1, NextCol + 4).Resize(1, 4).Value = _ Array(Cells(1, 3), Cells(1, 5), Cells(1, 2), Cells(1, 6)) Set ORange = Cells(1, NextCol + 4).Resize(1, 4) ' Второй расширенный фильтр - отбор строк, ' удовлетворяющих заданному условию. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=CRange, CopyToRange:=ORange ' Создание новой рабочей книги для размещения 296 Часть II Автоматизация Excel ' результата применения расширенного фильтра. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSN = WBN.Worksheets(1) ' Определение заголовка отчета. WSN.Cells(1, 1).Value = "Отчет о сделках заказчика " _ & ThisCust ' Копирование данных с текущего активного ' рабочего листа в новую рабочую книгу. WSO.Cells(1, NextCol + 4).CurrentRegion.Copy _ Destination:=WSN.Cells(3, 1) TotalRow = WSN.Cells(65536, 1).End(xlUp).Row + 1 WSN.Cells(TotalRow, 1).Value = "Всего" ' В англоязычной версии Excel: ' WSN.Cells(TotalRow, 2).FormulaR1C1 = "=SUM(R2C:R[-1]C)" ' WSN.Cells(TotalRow, 4).FormulaR1C1 = "=SUM(R2C:R[-1]C)" WSN.Cells(TotalRow, 2).FormulaR1C1Local = _ "=СУММ(R2C:R[-1]C)" WSN.Cells(TotalRow, 4).FormulaR1C1Local = _ "=СУММ(R2C:R[-1]C)" ' Стилевое форматирование отчета. WSN.Cells(3, 1).Resize(1, 4).Font.Bold = True WSN.Cells(TotalRow, 1).Resize(1, 4).Font.Bold = True WSN.Cells(1, 1).Font.Size = 18 WBN.SaveAs "C:\" & ThisCust & ".xls" WBN.Close SaveChanges:=False WSO.Select Set WSN = Nothing Set WBN = Nothing ' Очистить область вставки результата ' применения расширенных фильтров. Cells(1, NextCol + 2).Resize(1, 10).EntireColumn.Clear Next cell Cells(1, NextCol).EntireColumn.Clear MsgBox FinalCust - 1 & " отчетов были успешно созданы!" End Sub Подведем итог. Комбинация двух расширенных фильтров позволила создать 27 отчетов менее чем за 1 минуту (рис. 11.20). С учетом того, что опытные пользователи Excel создают один отчет в среднем за 2– 3 минуты, макрос RunReportForEachCustomer позволяет сэкономить около 1 часа рабочего времени. Анализ данных с помощью расширенного фильтра Глава 11 297 Рис. 11.20. Создание 27 отчетов менее чем за одну минуту — весьма непло& хой результат для комбинации двух расширенных фильтров! Автофильтр Автофильтр является упрощенным вариантом расширенного фильтра и обычно применяется посредством пользовательского интерфейса. Тем не менее, существует один аспект автофильтра, доступный исключиY тельно посредством VBA. При выборе команды Данные Фильтр Автофильтр (Data Filter AutoFilter) справа от названий столбцов в фильтруемом диапазоне появляются кнопки с изображением указывающей вниз стрелки. Чтобы скрыть кнопки для столбцов, по которым не нужно проводить фильтY рацию, воспользуйтесь VBA. Ниже приведен пример скрытия кнопок раскрыY вающегося списка для столбцов C (дата), E (количество), F (выручка), G (себестоимость) и H (прибыль): Sub AutoFilterCustom() Range("A1").AutoFilter Range("A1").AutoFilter Range("A1").AutoFilter Range("A1").AutoFilter Range("A1").AutoFilter End Sub Field:=3, Field:=5, Field:=6, Field:=7, Field:=8, VisibleDropDown:=False VisibleDropDown:=False VisibleDropDown:=False VisibleDropDown:=False VisibleDropDown:=False Параметр VisibleDropDown уникален тем, что он доступен только через программный код. Выполнить аналогичное действие посредством пользоваY 298 Часть II Автоматизация Excel тельского интерфейса Excel не представляется возможным. ‘‘Модифицируйте’’ подобным образом автофильтр, и вы прославитесь как знаток своего дела в глаY зах коллег, ничего не подозревающих об удивительных возможностях VBA. РеY зультат выполнения макроса AutoFilterCustom показан на рис. 11.21. Рис. 11.21. Скрыть кнопки раскрывающихся списков для столбцов, по которым не нужно проводить фильтрацию, возможно исключительно посредством VBA Следующий шаг Расширенный фильтр Excel предоставляет впечатляющие возможности по манипулированию исходными данными и созданию отчетов. Следующая глаY ва посвящена одному из краеугольных камней Excel — сводным таблицам. Комбинация расширенного фильтра и сводной таблицы YYYY это настоящая гремучая смесь, позволяющая делать с исходными данными все, что вам заY благорассудится! Глава 12 Ñâîäíûå òàáëèöû Впервые концепция сводных табY лиц была представлена компанией Lotus в продукте Improv. Одна из ключевых особенностей сводных таблиц состоит в возможноY сти быстрого суммирования больших объемов данных. Тем не менее, это далеко не единственное их применеY ние. В частности, сводные таблицы можно использовать для создания всевозможных отчетов. Сводные таблицы в различных версиях Excel Впервые сводные таблицы были представлены в Excel 95. В Excel 97 реаY лизация сводных таблиц была значиY тельно улучшена, а в Excel 2000 — кардинальным образом изменена. В Excel 2002 к сводным таблицам были добавлены несколько новых параметров. Создавая программный код в ExY cel 2003, следует уделить особое вниY мание его совместимости с Excel 2000 и Excel 97. И если совместимость с Excel 2000 достигается за счет внесеY ния в код нескольких небольших изY менений, то совместимость с Excel 97 требует его полного пересмотра. УчиY тывая, что Microsoft прекратила подY держку Excel 97, а возраст самого продукта превышает 7 лет, в этой главе будет использоваться кэш сводных таблиц, впервые представY 12 Сводные таблицы в различных версиях Excel ........ 299 Создание сводных таблиц с помощью пользовательского интерфейса Excel......................... 300 Создание сводных таблиц с помощью VBA ........................... 303 Создание отчета о структуре спроса на товары......................... 309 Создание отчета о структуре спроса на товары: завершающая стадия.................. 317 Создание отчета о прибыльности товаров ........... 326 Суммирование значений полей области данных сводной таблицы путем группирования .............................332 Дополнительные возможности сводных таблиц........................... 340 Сумма, среднее, количество, минимум, максимум и др. .........355 Дополнительные вычисления в полях области данных сводной таблицы ........................ 356 Следующий шаг............................ 361 300 Часть II Автоматизация Excel ленный в Excel 2000. Кроме того, в конце главы будет рассмотрен метод PivotTableWizard YYYY единственный способ создания кода, совместимого с Excel 97. Создание сводных таблиц с помощью пользовательского интерфейса Excel По имеющейся у Microsoft информации, сводные таблицы применяют около 7% пользователей Excel. В то же время, исследование компании MrExY cel Consulting показало, что сводные таблицы применяют около 42% опытных пользователей Excel. Как бы то ни было, о том, что такое сводная таблица, знают далеко не все. Рассмотрим создание простой сводной таблицы с помоY щью пользовательского интерфейса Excel. Предположим, что исходные данные занимают на рабочем листе свыше 12 000 строк (рис. 12.1). Рис. 12.1. Сводная таблица позволяет подсчитать суммарные значения для большого объема исходных данных Задача заключается в подсчете суммарного дохода по регионам (строки цеY левой таблицы) и товарам (столбцы целевой таблицы). Для ее решения создаY дим сводную таблицу, выполнив следующие действия. Сводные таблицы Глава 12 301 1. Выделите ячейку, принадлежащую диапазону исходных данных, и выY берите команду меню Excel Данные Сводная таблица (Data PivotY Table and PivotChart Report). 2. В открывшемся диалоговом окне Мастер сводных таблиц и диаграмм — шаг 1 из 3 (PivotTable and PivotChart Wizard — Step 1 of 3) установите переключатели В списке или базе данных Microsoft Office Excel (Microsoft Office Excel list or database) и Сводная таблица (PivotTable). 3. Убедитесь, что в поле Диапазон (Range) диалогового окна Мастер сводных таблиц и диаграмм — шаг 2 из 3 (PivotTable and PivotChart Wizard YYYY Step 2 of 3) указан верный адрес диапазона исходных данных. 4. В диалоговом окне Мастер сводных таблиц и диаграмм — шаг 3 из 3 (PivotTable and PivotChart Wizard — Step 3 of 3) установите переключаY тель Существующий лист (Existing worksheet) и укажите адрес первой ячейки диапазона, в который необходимо поместить сводную таблицу. Щелкните на кнопке Макет (Layout) (рис. 12.2). Рис. 12.2. Определите макет сводной таблицы 5. В диалоговом окне Мастер сводных таблиц и диаграмм — макет (PivotTable and PivotChart Wizard YYYY Layout) перетащите кнопку Регион и отпустите ее над областью Строка (ROW). Аналогичным образом пеY ретащите кнопку Товар и отпустите ее над областью Столбец (COLUMN). Наконец, перетащите кнопку Выручка и отпустите ее над областью Данные (DATA). Если столбец Выручка содержит только чиY словые сведения, при отпускании над областью Данные надпись на кнопке Выручка изменится на Сумма по полю Выручка (Sum of ВыручY ка), как показано на рис. 12.3. Щелкните на кнопке OK, для того чтобы вернуться к диалоговому окну Мастер сводных таблиц и диаграмм — шаг 3 из 3. 6. Щелкните на кнопке Готово (Finish). Практически мгновенно Excel сгенерирует сводную таблицу на основе исходных данных, как показано на рис. 12.4. 302 Часть II Автоматизация Excel Рис. 12.3. Чтобы определить макет сводной таблицы, перетащите кнопки, соответствующие требуемым столбцам исходных данных, и отпустите их над областями Строка, Столбец и Данные диалогового окна Мастер сводных таблиц и диаграмм — макет Рис. 12.4. Сводная таблица предельно лаконична Сводные таблицы Глава 12 303 Внимание В Excel 97 мастер сводных таблиц предполагает выполнение четырех шагов вместо трех. Шаг 3 мастера сводных таблиц Excel 97 заключается в определении макета сводной таблицы. Чтобы определить макет сводной таблицы в Excel 2003, щелк& ните на кнопке Макет (Layout) диалогового окна Мастер сводных таблиц и диаграмм — шаг 3 из 3 (PivotTable and PivotChart Wizard — Step 3 of 3). Поместив сводную таблицу на рабочий лист, вы можете изменять ее макет путем перетаскивания полей из окна Список полей сводной таблицы (PivotTable Field List) в сводную таблицу, и наоборот. Например, на рис. 12.5 показана сводная таблица, полученная в результате перетаскивания поля Регион в область столбцов, поля Товар YYYY в область строк, и добавления в обY ласть строк поля Заказчик. Рис. 12.5. Сводная таблица, полученная в результате перетаскивания поля Регион в область столбцов, поля Товар — в область строк, и добавления в область строк по& ля Заказчик Создание сводных таблиц с помощью VBA Чтобы создать сводную таблицу с помощью VBA в Excel 2000 и более поздY них версиях Excel, сперва необходимо создать объект кэша сводных таблиц, как показано далее: 304 Часть II Dim Dim Dim Dim Dim Set Автоматизация Excel WSD As Worksheet PTCache As PivotCache PT As PivotTable PRange As Range FinalRow As Long WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) После создания кэша сводных таблиц в него можно добавить новую сводY ную таблицу с помощью метода CreatePivotTable: Set PT = PTCache.CreatePivotTable(TableDestination:=WSD.Range( _ "J2"), TableName:="PivotTable1") Параметр TableDestination метода CreatePivotTable определяет размещение сводной таблицы, а параметр TableName — ее имя. Результат выполнения приведенной выше строки кода показан на рис. 12.6. Рис. 12.6. В результате выполнения метода CreatePivotTable Excel создает пустую свод& ную таблицу, состоящую из четырех ячеек Сводные таблицы Глава 12 305 Excel не пересчитывает сводную таблицу при добавлении полей к ее макету с помощью пользовательского интерфейса, однако по умолчанию пересчитыY вает сводную таблицу на каждом шаге ее построения с помощью VBA. Чтобы повысить эффективность программного кода, временно запретите пересчет сводной таблицы с помощью свойства ManualUpdate: PT.ManualUpdate = True Теперь все готово для создания макета сводной таблицы с помощью VBA. Воспользовавшись методом AddFields, добавьте поля к области строк, обY ласти столбцов или области страницы сводной таблицы: ' Добавить поля к области строк и области столбцов сводной таблицы. PT.AddFields RowFields:=Array("Товар", "Заказчик"), _ ColumnFields:="Регион" Чтобы добавить поле Выручка к области данных сводной таблицы, устаY новите значение свойства Orientation этого поля равным xlDataField (рассматривается в следующем разделе). Подсчет суммы чисел вместо количества значений При добавлении поля Выручка к области данных сводной таблицы Excel вполне логично предполагает, что вы хотите подсчитать сумму выручки. К соY жалению, это справедливо только при условии, что все без исключения ячейY ки столбца Выручка содержат числовые данные. Если хотя бы одна ячейка будет содержать нечисловые данные (например, окажется пустой), вместо суммы чисел Excel подсчитает количество значений. Определяя макет сводной таблицы с помощью пользовательского интерY фейса, убедитесь, что название кнопки Выручка изменилось в результате ее перетаскивания в область Данные (DATA) на Сумма по полю Выручка (Sum of Выручка), а не на Количество по полю Выручка (Count of Выручка). Чтобы изменить функцию подсчета количества значений на функцию подсчета сумY мы чисел, исправьте исходные данные или же дважды щелкните на кнопке Количество по полю Выручка, после чего ее название изменится на Сумма по полю Выручка. Для того чтобы указать на необходимость применения функции подсчета суммы чисел в коде VBA, установите значение свойства Function поля Выручка равным xlSum, как показано ниже: ' Определить область данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 End With Задав все необходимые параметры сводной таблицы, пересчитайте ее, усY тановив значение свойства ManualUpdate равным False, а затем вновь заY претите автоматический пересчет, установив значение свойства ManualUpdate равным True, как показано далее: 306 Часть II Автоматизация Excel ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True Ниже приведен полный код макроса, создающего сводную таблицу, анаY логичную показанной на рис. 12.5: Sub CreatePivot() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Dim FinalRow As Long Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Добавить поля к области строк и области столбцов сводной таблицы. PT.AddFields RowFields:=Array("Товар", "Заказчик"), _ ColumnFields:="Регион" ' Определить область данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 End With ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub Перемещение или изменение части сводной таблицы Excel не разрешает перемещать или изменять часть сводной таблицы. К примеру, создайте макрос, удаляющий столбец, который содержит общую сумму по строкам сводной таблицы (в данном случае YYYY столбец O). В резульY тате выполнения макроса будет сгенерирована ошибка времени выполнеY ния 1004, как показано на рис. 12.7. Сводные таблицы Глава 12 307 Рис. 12.7. Excel не разрешает перемещать или изменять часть сводной таблицы. Чтобы обойти это ограничение, скопируйте содер& жимое ячеек сводной таблицы в другое место Определение размера сводной таблицы Определить размер сводной таблицы заранее достаточно сложно. Например, рассматриваемая сводная таблица может содержать как 5, так и 6 столбцов в заY висимости от того, была ли заключена в западном регионе хотя бы одна сделка. Чтобы обратиться ко всей сводной таблице, необходимо воспользоваться ее свойством TableRange2. Поскольку Excel не разрешает перемещать или изменять часть сводной таблицы, рекомендуется скопировать содержимое ячеек сводной таблицы в другое место и удалить исходную таблицу. Рассмотрим макрос CreateSummaryReportUsingPivot, который создает небольшую сводную таблицу. Чтобы запретить добавление к сводной таблице столбца Общий итог (Grand Total) и одноименной строки, установите равными False значения свойств ColumnGrand и RowGrand, соответственно. Диапазон ячеек PT.TableRange2 охватывает всю сводную таблицу, включая строку с кнопкой Сумма по полю Выручка (Sum of Выручка). Чтобы избавиться от этой строки, необходимо сместить ссылку PT.TableRange2 на одну строку вниз (Offset(1, 0)). Если сводная таблица содержит нескольY ко строк с избыточной информацией, сместите ссылку PT.TableRange2 на соответствующее число строк. Для вставки содержимого ячеек диапазона PT.TableRange2.Offset(1, 0) в область, начинающуюся ячейкой J10, применяется метод PasteSpecial. На рис. 12.8 показано содержимое рабочего листа после копирования ячеек сводY ной таблицы. Чтобы удалить сводную таблицу, ‘‘очистите’’ диапазон ячеек PT.TableRange2 с помощью метода Clear. Если вы затем собираетесь форматировать рабочий лист, удалите объект PivotCache из памяти, присвоив соответстY вующей переменной (в данном случае PTCache) значение Nothing. 308 Часть II Автоматизация Excel Рис. 12.8. Содержимое рабочего листа перед удалением свод& ной таблицы Sub CreateSummaryReportUsingPivot() ' Этот макрос создает статический отчет ' на основе сводной таблицы. Dim Dim Dim Dim Set WSD As Worksheet PTCache As PivotCache PT As PivotTable PRange As Range WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить область строк и область столбцов сводной таблицы. PT.AddFields RowFields:="Регион", ColumnFields:="Товар" ' Определить область данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 End With ' Запретить создание итогового столбца и итоговой строки. With PT .ColumnGrand = False .RowGrand = False .NullString = "0" End With Сводные таблицы Глава 12 309 ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Скопировать содержимое ячеек диапазона ' PT.TableRange2.Offset(1, 0) в область, ' начинающуюся ячейкой J10. PT.TableRange2.Offset(1, 0).Copy WSD.Range("J10").PasteSpecial xlPasteValues ' Содержимое рабочего листа на текущий ' момент показано на рис. 12.8. ' Удалить сводную таблицу и объект кэша сводных таблиц. PT.TableRange2.Clear Set PTCache = Nothing End Sub Далее в этой главе рассматриваются более сложные примеры использоваY ния сводных таблиц. Создание отчета о структуре спроса на товары Отчет о структуре спроса на товары может быть полезен менеджерам по проY дажам, поскольку он позволяет получить наглядное представление об объеY мах закупок товаров разными заказчиками. Отчет, показанный на рис. 12.9, содержит список заказчиков в порядке убывания общего объема закупок в трех регионах: центральном, восточном и западном. Рис. 12.9. 90% этого отчета создано с помощью сводной таблицы 310 Часть II Автоматизация Excel Чтобы создать такой отчет, следует воспользоваться сводной таблицей. К сожалению, конечный результат применения сводной таблицы вряд ли поY нравится менеджерам по продажам. В частности, сводная таблица не предуY сматривает вставку разрыва страницы между частями отчета, соответствуюY щими разным товарам. Тем не менее, первый шаг в подготовке отчета о структуре спроса на товаY ры состоит в создании сводной таблицы с полями Товар и Заказчик в обласY ти строк, полем Регион в области столбцов и полем Выручка в области данY ных (рис. 12.10). Рис. 12.10. В основе отчета о структуре спроса на товары лежит сводная таблица Ниже перечислены очевидные недостатки стандартной сводной таблицы с двумя полями в области строк. Внешний вид стандартной сводной таблицы оставляет желать лучшего. Как показано на рис. 12.10, значение ‘‘ABC’’ встречается в столбце Товар всего один раз, а следующие за ним 26 ячеек и вовсе оставлены пустыми. Это одна из наиболее серьезных недоработок сводных табY лиц, устранить которую весьма непросто. Безусловно, большинство пользователей догадаются, о каком товаре идет речь, однако если часть отчета, посвященная товару ABC, будет занимать 2 и более страницы, могут возникнуть определенные трудности. Кроме того, подобный способ представления данных затрудняет их повторное использование. Сводные таблицы Глава 12 Область данных отчета содержит пустые ячейки вместо нулей. Как поY казано на рис. 12.10, заказчик BCD LTD. приобрел товар ABC только в западном регионе, что побудило Excel оставить пустыми ячейки, соY ответствующие объемам закупок товара ABC заказчиком BCD LTD. В восточном и центральном регионах. Это может не понравиться опытным пользователям Excel, привыкшим перемещаться по блокам данных с помощью последовательного нажатия клавиши <End> и одY ной из клавиш со стрелками. Название отчета заслуживает отдельного внимания. Вряд ли комуY нибудь понравится, чтобы предоставленный ему отчет имел заголовок Сумма по полю Выручка (Sum of Выручка). Заголовки некоторых столбцов сводной таблицы избыточны. К примеY ру, слово ‘‘Регион’’, помещенное в ячейку L2 (см. рис. 12.10), и вовсе не относится к отчету. Стандартный порядок сортировки (по алфавиту), применяемый в сводной таблице, не всегда используется на практике. В частности, менеджеры по продажам предпочитают, чтобы список заказчиков был отсортирован в поY рядке убывания суммы закупок товара. Кроме того, последовательность регионов ‘‘ВостокYYЗападYYЦентр’’ не соответствует их естественному геоY графическому расположению (‘‘ЗападYYЦентрYYВосток’’). Автоматически сгенерированные границы определенно не красят отчет. При отображении числовых значений используется формат Общий (General), хотя для подобных отчетов больше подходит формат с раздеY лителем групп разрядов и округлением чисел до тысяч или миллионов. Сводные таблицы не поддерживают вставку разрыва страницы между различными частями отчета. Если отчет предназначен для нескольких менеджеров по продажам, каждый из которых курирует определенный товар, разрывы страниц придется добавлять самостоятельно. Ввиду невозможности вставки разрывов страниц между различными частями отчета, целесообразно отказаться от автоматического создания строк с промежуточными итогами сводной таблицы и добавить строки с промежуточными итогами и разрывы страниц вручную с помощью метода Subtotal. Excel генерирует строки с промежуточными итогами при наличии в области строк сводной таблицы двух и более полей (на рис. 12.10 показана строка с промежуточными итогами для поля Товар). Например, при наличии в области строк 4Yх полей Excel сгенериY рует 3 строки с промежуточными итогами для 3Yх первых полей. К счастью, каждый из перечисленных выше недостатков можно устранить либо путем изменения параметров сводной таблицы, либо с помощью проY граммного кода. В последнем случае содержимое сводной таблицы следует предварительно скопировать в другую область рабочего листа или на новый рабочий лист. 311 312 Часть II Автоматизация Excel Заполнение значениями пустых ячеек в области данных Пустые ячейки начали досаждать пользователям со времен первой реалиY зации сводных таблиц в Excel 95. Начиная с Excel 97, Microsoft предоставляет возможность указать значение, которое будет отображаться в пустых ячейках. Чтобы задать значение, которое будет отображаться в пустых ячейках, откройY те диалоговое окно Параметры сводной таблицы (PivotTable Options). Для этого щелкните на кнопке Параметры (Options) диалогового окна Мастер сводных таблиц и диаграмм — шаг 3 из 3 (PivotTable and PivotChart Wizard — Step 3 of 3) или выберите команду Параметры таблицы (Table Options) из расY крывающегося списка Сводная таблица (PivotTable) панели инструментов Сводные таблицы (PivotTable). Установите флажок Для пустых ячеек отображать (For empty cells, show) и введите 0 в расположенном справа от флажка текстовом поле (рис. 12.11). Рис. 12.11. Установите флажок Для пустых ячеек отображать и введите значение, которое будет отображаться в пустых ячейках области данных сводной таблицы, в расположенном справа от флажка текстовом поле Чтобы выполнить эквивалентное действие в VBA, установите значение свойства объекта сводной таблицы NullString равным "0". На заметку Несмотря на то что в коде VBA значение, которое будет отображаться в пустых ячейках, задается как текстовый нуль, Excel помещает в пустые ячейки число& вой нуль. Сводные таблицы Глава 12 313 Изменение порядка сортировки списка заказчиков Средства пользовательского интерфейса Excel позволяют отсортировать список заказчиков в порядке убывания суммы закупок товара. Дважды щелкY ните на кнопке сводной таблицы Заказчик, в результате чего откроется диаY логовое окно Вычисление поля сводной таблицы (PivotTable Field), показанY ное на рис. 12.12. Рис. 12.12. Чтобы добраться до параметров сортировки сводной таблицы, щелкните на кнопке Дополнительно Щелкните на кнопке Дополнительно (Advanced), в результате чего откроY ется диалоговое окно Дополнительные параметры поля сводной таблицы (PivotTable Field Advanced Options). Установите переключатель По убыванию (Descending) и выберите из расположенного под переключателем раскрываюY щегося списка значение Сумма по полю Выручка (Sum of Выручка), как покаY зано на рис. 12.13. Рис. 12.13. Диалоговое окно Дополнительные параметры поля сводной таблицы позволяет выбрать порядок сорти& ровки содержимого поля сводной таблицы Чтобы выполнить эквивалентное действие в VBA, воспользуйтесь методом AutoSort: PT.PivotFields("Заказчик").AutoSort Order:=xlDescending, _ Field:="Сумма по полю Выручка" 314 Часть II Автоматизация Excel Изменение порядка следования столбцов сводной таблицы вручную Последовательность регионов YYYY столбцов сводной таблицы YYYY ‘‘ВостокYY ЗападYYЦентр’’ не соответствует их естественному географическому распоY ложению (‘‘ЗападYYЦентрYYВосток’’), что может смутить конечных пользоY вателей отчета. Microsoft предлагает весьма экстравагантный способ изменения порядка следования столбцов сводной таблицы, получивший название ручной сортиY ровки. Как показано на рис. 12.10, по умолчанию Excel выстраивает столбцы сводной таблицы по алфавиту: Восток, Запад, Центр (заголовки столбцов находятся в ячейках L3:N3, соответственно). Чтобы изменить порядок следоY вания столбцов с помощью пользовательского интерфейса, введите в ячейке N3 слово ‘‘Восток’’. Как по мановению волшебной палочки, Excel сместит столбцы Запад и Центр на одну позицию влево и перенесет столбец Восток на место бывшего столбца Центр (рис. 12.14). Рис. 12.14. Чтобы изменить стандартный порядок следования столб& цов сводной таблицы Восток, Запад, Центр с помощью пользова& тельского интерфейса, введите в ячейке N3 слово “Восток” Чтобы выполнить эквивалентное действие в VBA, измените значение свойства Position соответствующего столбца сводной таблицы. Поскольку Сводные таблицы Глава 12 315 гарантии того, что столбец с заданным заголовком будет присутствовать в исY ходных данных, нет, отключите обработку ошибок, как показано ниже (обработке ошибок посвящена глава 23, ‘‘Обработка ошибок’’): On Error Resume Next PT.PivotFields("Регион").PivotItems("Восток").Position = 3 On Error GoTo 0 Изменение формата отображения числовых значений Чтобы изменить формат отображения числовых значений сводной таблиY цы с помощью пользовательского интерфейса, дважды щелкните на кнопке Сумма по полю Выручка (Sum of Выручка). В открывшемся диалоговом окне Вычисление поля сводной таблицы (PivotTable Field) щелкните на кнопке Формат (Number) и выберите требуемый формат с помощью диалогового окна Формат ячеек (Format Cells). При работе с большими числами рекомендуется использовать формат с разделителем групп разрядов. Ниже приведен пример задания маски формата с разделителем групп разрядов в VBA: PT.PivotFields("Сумма по полю Выручка").NumberFormat = "#,##0" Зачастую суммы заказов товаров исчисляются в тысячах, а то и в миллиоY нах. Чтобы округлить числовое значение до тысяч, добавьте в конец маски формата запятую и, при желании, аббревиатуру ‘‘K’’: PT.PivotFields("Сумма по полю Выручка").NumberFormat = "#,##0,K" Некоторые компании по старой привычке используют в качестве показатеY ля тысяч аббревиатуру ‘‘M’’, а в качестве показателя миллионов YYYY аббревиаY туру ‘‘MM’’. Чтобы использовать в качестве показателя тысяч аббревиатуру ‘‘M’’, предварите ее символом обратной косой черты: PT.PivotFields("Сумма по полю Выручка").NumberFormat = "#,##0,\M" Символ обратной косой черты можно заменить двойными кавычками. Особенность употребления двойных кавычек внутри строки в кавычках в коде VBA заключается в необходимости дублировать каждый символ двойной каY вычки. Ниже приведен пример задания маски #,##0.0,,"MM", используюY щейся для округления числовых значений до десятков миллионов с аббревиаY турой ‘‘MM’’: PT.PivotFields("Сумма по полю Выручка").NumberFormat = _ "#,##0.0,,""MM""" Запрет автоматического добавления промежуточных итогов Excel автоматически генерирует промежуточные итоги при наличии в обY ласти строк сводной таблицы двух и более полей. Строки с промежуточными итогами создаются для всех полей, за исключением последнего. Ввиду невозY можности вставки разрывов страниц между частями отчета, относящимися к 316 Часть II Автоматизация Excel разным товарам, целесообразно отказаться от автоматического добавления промежуточных итогов сводной таблицы и добавить промежуточные итоги и разрывы страниц вручную с помощью метода Subtotal. Чтобы запретить автоматическое добавление промежуточных итогов для поля Товар с помощью пользовательского интерфейса Excel, дважды щелкY ните на кнопке Товар и установите переключатель Нет (None) в открывшемся диалоговом окне Вычисление поля сводной таблицы (PivotTable Field), как показано на рис. 12.15. Рис. 12.15. Строки с промежуточными итогами созда& ются автоматически при наличии в области строк сводной таблицы двух и более полей. Чтобы запретить добавление промежуточных итогов для некоторого поля, дважды щелкните на кнопке, соответствующей этому полю, и установите переключатель Нет в от& крывшемся диалоговом окне Чтобы выполнить аналогичное действие в VBA, придется изрядно потрудитьY ся, а именно установить значение свойства поля Subtotals равным массиву, соY стоящему из 12 значений False. Первое значение False запрещает автоматичеY ское добавление промежуточных итогов, второе значение False — подсчет сумY мы, третье значение False — подсчет количества и т.д. (полный синтаксис свойства Subtotals приводится в справочной системе VBA). Следующий код заY прещает создание строки с промежуточными итогами для поля Товар: PT.PivotFields("Товар").Subtotals = Array(False, False, False, _ False, False, False, False, False, False, False, False, False) Чтобы сделать код более удобочитаемым, можно создать переменную, храY нящую массив из 12 значений False: NoSubtotalArray = Array(False, False, False, False, False, _ False, False, False, False, False, False, False) PT.PivotFields("Товар").Subtotals = NoSubtotalArray PT.PivotFields("Дата").Subtotals = NoSubtotalArray Запрет подсчета общей суммы по столбцам Логичным продолжением запрета автоматического добавления промежуY точных итогов сводной таблицы будет запрет подсчета общей суммы по Сводные таблицы Глава 12 317 столбцам, помещаемой в строку Общий итог (Grand Total). Откройте диалоY говое окно Параметры сводной таблицы (PivotTable Options) (см. рис. 12.11). Чтобы запретить создание строки Общий итог, снимите флажок Общая сумма по столбцам (Grand totals for columns) (чтобы запретить создание одY ноименного столбца, сбросьте флажок Общая сумма по строкам (Grand totals for rows)). Ниже приведен соответствующий код VBA: PT.ColumnGrand = False Создание отчета о структуре спроса на товары: завершающая стадия В предыдущем разделе были рассмотрены изменения, которые можно вноY сить непосредственно в сводную таблицу Excel. Все оставшиеся действия по созданию отчета о структуре спроса на товары требуют копирования значений сводной таблицы (но не саму таблицу!) в другое место. На рис. 12.16 показана сводная таблица после выполнения всех действий, описанных в предыдущем разделе. Рис. 12.16. Менее 1 секунды и 30 строк программного кода понадобилось для того, чтобы преодолеть более 90% пути к отчету о структуре спроса на товары 318 Часть II Автоматизация Excel Создание новой рабочей книги Предположим, что отчет о структуре спроса на товары необходимо размесY тить в новой рабочей книге, чтобы отправить ее затем по электронной почте. С целью сделать код более универсальным, создадим объектные переменные для текущей рабочей книги, новой рабочей книги и первого рабочего листа в новой рабочей книге (см. практикум ‘‘Использование двух расширенных фильтров для создания отчетов по каждому заказчику’’ на с. 293). Добавьте следующий код в начало макроса: Dim Dim Dim Dim Dim Dim Dim WSD As Worksheet WSR As Worksheet WBO As Workbook WBN As Workbook PTCache As PivotCache PT As PivotTable PRange As Range Set WBO = ActiveWorkbook Set WSD = Worksheets("Данные") После построения и пересчета сводной таблицы создайте новую рабочую книгу, как показано ниже: ' Создать новую рабочую книгу с одним листом. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSR = WBN.Worksheets(1) WSR.Name = "Отчет" ' Создать заголовок отчета. With WSR.[A1] .Value = "Отчет о структуре спроса на товары" .Font.Size = 14 End With Копирование содержимого сводной таблицы Среди недостатков сводной таблицы, показанной на рис. 12.16, следует отY метить наличие границ, неудачный заголовок и совершенно ненужное слово ‘‘Регион’’ в ячейке L2. Все это можно устранить путем копирования содержиY мого диапазона ячеек PT.TableRange2 (за исключением первой строки) и его вставки в новый рабочий лист с помощью метода PasteSpecial со знаY чением параметра Paste, равным xlPasteValuesAndNumberFormats. Как уже отмечалось, диапазон ячеек PT.TableRange2 содержит одну изY быточную строку — строку 2 (см. рис. 12.16). Более сложные сводные таблицы с несколькими полями в области столбцов или области страницы будут соY держать несколько таких строк (определить точное количество ненужных строк поможет анализ макета сводной таблицы). Чтобы исключить избыточY ные строки из копируемого диапазона ячеек, воспользуйтесь его свойством Offset. Можно справедливо заметить, что смещение ссылки на диапазон ячеек PT.TableRange2 приведет к захвату лишних строк под сводной таблиY цей, однако это несущественно, так как эти строки пустые. После копироваY Сводные таблицы Глава 12 319 ния содержимого сводной таблицы ее можно удалить с рабочего листа, а соотY ветствующий объект PivotCache — из памяти компьютера: ' Скопировать содержимое сводной таблицы и поместить ' его на рабочий лист "Отчет". Свойство Offset используется ' для исключения из копируемого диапазона ячеек строки с ' заголовком сводной таблицы. PT.TableRange2.Offset(1, 0).Copy WSR.[A3].PasteSpecial Paste:=xlPasteValuesAndNumberFormats PT.TableRange2.Clear Set PTCache = Nothing Обратите внимание, что метод PasteSpecial копирует только значения и форматы чисел, что позволяет избавиться как от границ, так и от самой струкY туры сводной таблицы. Последнее необходимо для возможности вставки в скопированные данные новых строк. Улучшение внешнего вида отчета Как показано на рис. 12.17, большинство ячеек в столбце A пусты. Рис. 12.17. Пустые ячейки — это одна из наиболее сущест& венных недоработок сводных таблиц, устранить которую весьма непросто Чтобы заполнить пустые ячейки столбца A соответствующими значениями с помощью пользовательского интерфейса Excel, выполните следующие действия. 320 Часть II Автоматизация Excel 1. Выделите все ячейки столбца A, в которых должно содержаться наимеY нование товара. 2. Выберите команду меню Правка Перейти (Edit Go To), в результате чего откроется диалоговое окно Переход (Go To). Щелкните на кнопке Выделить (Special) и установите в открывшемся диалоговом окне Выделение группы ячеек (Go To Special) переключатель Пустые ячейки (Blanks) (рис. 12.18). Рис. 12.18. Выделите пустые ячейки и заполните их форму& лой, ссылающейся на ячейку выше 3. Заполните пустые ячейки R1C1Yформулой, ссылающейся на ячейку выше (=R[-1]C). Чтобы сделать это с помощью пользовательского инY терфейса, введите знак равенства, нажмите клавишу <↑>, а затем YYYY комбинацию клавиш <Ctrl+Enter>. 4. Выделите все ячейки столбца A, в которых содержится наименование товара. Необходимость этого шага обусловлена тем, что функция спеY циальной вставки (см. следующий шаг) не поддерживает работу с неY смежными диапазонами ячеек. 5. Скопируйте выделенные ячейки и вставьте их с помощью команды Правка Специальная вставка (Edit Paste Special), установив в отY крывшемся диалоговом окне Специальная вставка (Paste Special) переY ключатель Значения (Values). Сводные таблицы Глава 12 321 Достижение аналогичного результата с помощью VBA можно разбить на 3 этапа. 1. Вычисление номера последней строки отчета. 2. Вставка формулы =R[-1]C в пустые ячейки столбца A. 3. Замена формул значениями. Ниже приведен соответствующий код VBA: ' Вычислить номер последней строки отчета по столбцу B. FinalReportRow = WSR.Range("B65536").End(xlUp).Row With Range("A3").Resize(FinalReportRow - 2, 1) With .SpecialCells(xlCellTypeBlanks) .FormulaR1C1 = "=R[-1]C" End With .Value = .Value End With Стилевое форматирование отчета Прежде чем добавить строки с промежуточными итогами, применим к отY чету стилевое форматирование. Для этого выделим полужирным шрифтом заголовки столбцов отчета (строка 3) и выровняем их по правому краю ячейки (за исключением столбцов A и B, которые нужно выровнять по левому краю ячейки). Последняя строка приведенного ниже кода указывает на необходиY мость печати на каждой странице трех первых строк отчета: ' Стилевое форматирование отчета: ' - выделение полужирным шрифтом заголовков столбцов; ' - выравнивание заголовков столбцов по правому краю ' (за исключением столбцов A-B, которые ' выравниваются по правому краю). Selection.Columns.AutoFit Range("A3").EntireRow.Font.Bold = True Range("A3").EntireRow.HorizontalAlignment = xlRight Range("A3:B3").HorizontalAlignment = xlLeft ' Печатать на каждой странице строки 1-3 отчета. WSR.PageSetup.PrintTitleRows = "$1:$3" Добавление промежуточных итогов Чтобы добавить промежуточные итоги с помощью пользовательского инY терфейса Excel, выделите область данных отчета (включая заголовки столбY цов) и выберите команду меню Данные Итоги (Data Subtotals). Проверьте, чтобы в открывшемся диалоговом окне Промежуточные итоги (Subtotals) был установлен флажок Конец страницы между группами (Page breaks between groups), как показано на рис. 12.19. 322 Часть II Автоматизация Excel Рис. 12.19. При добавлении промежуточных итогов с помощью команды Данные Итоги Excel позволяет вставить разрыв страницы между группами данных Если отчет о структуре спроса на товары всегда содержит столбцы Запад, Центр и Восток, для добавления промежуточных итогов можно воспользоY ваться следующим кодом: ' Добавление промежуточных итогов со вставкой ' разрыва страницы между группами данных. Selection.Subtotal GroupBy:=1, Function:=xlSum, _ TotalList:=Array(3, 4, 5, 6), Replace:=True, PageBreaks:=True, _ SummaryBelowData:=True К сожалению, выполнение этого кода приведет к ошибке при изменении количества столбцов, соответствующих регионам. Решение этой проблемы состоит в динамическом создании массива столбцов, по которым нужно подY водить промежуточные итоги: FinalCol = Cells(3, 255).End(xlToLeft).Column ReDim Preserve TotColumns(1 To FinalCol - 2) For i = 3 To FinalCol TotColumns(i - 2) = i Next i Selection.Subtotal GroupBy:=1, Function:=xlSum, _ TotalList:=TotColumns, Replace:=True, PageBreaks:=True, _ SummaryBelowData:=True Наконец, необходимо подобрать ширину столбцов отчета в соответствии с добавленными в него строками: ' Автоподбор ширины столбцов отчета. GrandRow = Range("A65536").End(xlUp).Row Cells(GrandRow, 3).Resize(1, 4).Columns.AutoFit Cells(GrandRow, 3).Resize(1, 4).NumberFormat = "#,##0,K" ' Добавить разрыв страницы перед ' строкой "Общий итог" (Grand Total). WSR.HPageBreaks.Add Before:=Cells(GrandRow, 1) Сводные таблицы Глава 12 323 Результирующий код Ниже приведен полный код макроса, создающего отчет о структуре спроса на товары: Sub ProductLineManagerReport() Dim WSD As Worksheet Dim WSR As Worksheet Dim WBO As Workbook Dim WBN As Workbook Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Dim TotColumns() Dim FinalRow As Long Dim FinalReportRow As Long Dim FinalCol As Integer Dim i As Integer Dim GrandRow As Long Dim NoSubtotalArray As Variant Set WBO = ActiveWorkbook Set WSD = Worksheets("Данные") ' Удалить все существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк. PT.AddFields RowFields:=Array("Товар", "Заказчик"), _ ColumnFields:="Регион" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Запретить вставку строки "Общий итог" (Grand Total). PT.ColumnGrand = False 324 Часть II Автоматизация Excel ' Запретить Excel автоматически вставлять ' строки с промежуточными итогами. NoSubtotalArray = Array(False, False, False, False, False, _ False, False, False, False, False, False, False) PT.PivotFields("Товар").Subtotals = NoSubtotalArray ' Отсортировать список заказчиков ' в порядке убывания суммы закупок товара. PT.PivotFields("Заказчик").AutoSort Order:=xlDescending, _ Field:="Сумма по полю Выручка" ' ' ' ' Изменить порядок следования столбцов сводной таблицы так, чтобы был соблюден естественный порядок следования регионов "Запад"-"Центр"-"Восток". Отключить обработку ошибок на случай, если столбец "Восток" не существует. On Error Resume Next PT.PivotFields("Регион").PivotItems("Восток").Position = 3 On Error GoTo 0 ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Создать новую рабочую книгу с одним листом. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSR = WBN.Worksheets(1) WSR.Name = "Отчет" ' Создать заголовок отчета. With WSR.[A1] .Value = "Отчет о структуре спроса на товары" .Font.Size = 14 End With ' ' ' ' Скопировать содержимое сводной таблицы и поместить его на рабочий лист "Отчет". Свойство Offset используется для исключения из копируемого диапазона ячеек строки с заголовком сводной таблицы. PT.TableRange2.Offset(1, 0).Copy WSR.[A3].PasteSpecial Paste:=xlPasteValuesAndNumberFormats PT.TableRange2.Clear Set PTCache = Nothing ' Вычислить номер последней строки отчета по столбцу B. FinalReportRow = WSR.Range("B65536").End(xlUp).Row With Range("A3").Resize(FinalReportRow - 2, 1) With .SpecialCells(xlCellTypeBlanks) .FormulaR1C1 = "=R[-1]C" End With .Value = .Value End With ' Стилевое форматирование отчета: ' - выделение полужирным шрифтом заголовков столбцов; ' - выравнивание заголовков столбцов по правому краю ' (за исключением столбцов A-B, которые ' выравниваются по правому краю). Selection.Columns.AutoFit Сводные таблицы Глава 12 325 Range("A3").EntireRow.Font.Bold = True Range("A3").EntireRow.HorizontalAlignment = xlRight Range("A3:B3").HorizontalAlignment = xlLeft ' Печатать на каждой странице строки 1-3 отчета. WSR.PageSetup.PrintTitleRows = "$1:$3" ' Добавление промежуточных итогов со вставкой ' разрыва страницы между группами данных. 'Selection.Subtotal GroupBy:=1, Function:=xlSum, _ ' TotalList:=Array(3, 4, 5, 6), Replace:=True, _ ' PageBreaks:=True, SummaryBelowData:=True Выполнение приведенного выше кода приведет к ошибке при изменении количества столбцов, соответствующих регионам. Решение этой проблемы состоит в динамическом создании массива столбцов, по которым нужно подводить промежуточные итоги. FinalCol = Cells(3, 255).End(xlToLeft).Column ReDim Preserve TotColumns(1 To FinalCol - 2) For i = 3 To FinalCol TotColumns(i - 2) = i Next i Selection.Subtotal GroupBy:=1, Function:=xlSum, _ TotalList:=TotColumns, Replace:=True, PageBreaks:=True, _ SummaryBelowData:=True ' ' ' ' ' Автоподбор ширины столбцов отчета. GrandRow = Range("A65536").End(xlUp).Row Cells(GrandRow, 3).Resize(1, 4).Columns.AutoFit Cells(GrandRow, 3).Resize(1, 4).NumberFormat = "#,##0,K" ' Добавить разрыв страницы ' перед строкой "Общий итог" (Grand Total). WSR.HPageBreaks.Add Before:=Cells(GrandRow, 1) ' Изменить заголовок последнего столбца ' отчета с "Общий итог" (Grand Total) на "Всего". Cells(3, FinalCol).Value = "Всего" MsgBox "Отчет о структуре спроса на товары успешно создан." End Sub На рис. 12.20 показана первая страница отчета, созданного с помощью макроса ProductLineManagerReport. Рис. 12.20. Без применения сводных таблиц для создания подоб& ного отчета понадобилось бы разработать гораздо более сложный код VBA 326 Часть II Автоматизация Excel Создание отчета о прибыльности товаров Отчет о структуре спроса на товары продемонстрировал лишь часть возY можностей сводных таблиц. В частности, область данных рассмотренной выY ше сводной таблицы содержала всего одно поле YYYY Выручка. Область данных сводной таблицы, использующейся для создания отчета о прибыльности товаров, содержит четыре поля YYYY Выручка, Количество, Себестоимость и Прибыль. В итоговом отчете должна быть отражена следуюY щая информация: общее количество проданного товара, общая выручка от продажи товара, средняя цена единицы товара, общая себестоимость проданY ного товара, средняя себестоимость единицы товара, валовая прибыль и валоY вая прибыль в процентах. На рис. 12.21 показан макет сводной таблицы, созданный с помощью польY зовательского интерфейса Excel. Рис. 12.21. Область данных сводной таблицы может включать несколько полей По умолчанию Excel помещает поля области данных сводной таблицы в последовательные ячейки столбца, как показано на рис. 12.22. Рис. 12.22. Стандартный способ представления по& лей данных сводной таблицы не подходит для соз& дания отчета Глава 12 327 Сводные таблицы С помощью пользовательского интерфейса Excel перетащите кнопку Данные на кнопку Товар и отпустите ее. К сожалению, сводная таблица, поY казанная на рис. 12.23, все еще не пригодна для создания отчета. Выходом из сложившейся ситуации является перенесение поля Данные в область столбцов сводной таблицы. Начав перетаскивать кнопку Данные, обY ратите внимание на форму курсора мыши. Область сводной таблицы, в котоY рую будет перенесено поле, обозначена синим цветом (рис. 12.24). Область строк Область данных Область столбцов Рис. 12.23. Перемена мест полей Данные и Товар не привела к желаемому результату Удалить поле Область страницы Рис. 12.24. Область сводной таблицы, в которую будет перенесено поле, обо& значена синим цветом Результат перенесения поля Данные в область столбцов сводной таблицы показан на рис. 12.25. Рис. 12.25. После перенесения поля Данные в область столбцов сводная таблица стала напоминать вполне привычный финансовый отчет Если в область данных сводной таблицы помещается несколько полей, ExY cel автоматически объединяет их в виртуальное поле Данные. Чтобы опредеY лить внешний вид сводной таблицы с помощью VBA, воспользуйтесь методом AddFields. Сводная таблица, показанная на рис. 12.22, может быть получена в резульY тате выполнения следующей строки кода: PT.AddFields RowFields:=Array("Товар", "Данные") А вот как добиться результата, показанного на рис. 12.23: PT.AddFields RowFields:=Array("Данные", "Товар") 328 Часть II Автоматизация Excel Чтобы поместить поле Данные в область столбцов сводной таблицы, восY пользуйтесь приведенным ниже кодом: PT.AddFields RowFields:="Товар", ColumnFields:="Данные" После добавления в область столбцов сводной таблицы поля Данные опреY делите поля области данных: ' Определение полей области данных. With PT.PivotFields("Количество") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0" .Name = "Общее количество" End With With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0" .Name = "Общая выручка" End With With PT.PivotFields("Себестоимость") .Orientation = xlDataField .Function = xlSum .Position = 4 .NumberFormat = "#,##0" .Name = "Общая себестоимость" End With With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 6 .NumberFormat = "#,##0" .Name = "Валовая прибыль" End With Определение вычисляемых полей области данных Сводные таблицы поддерживают два типа формул. Наиболее часто испольY зуемый тип формул предназначен для добавления к сводной таблице вычисY ляемых полей. Подсчет значений вычисляемого поля осуществляется на осноY ве итоговых значений полей, входящих в формулу вычисляемого поля. НаY пример, при создании поля СредняяЦена с помощью формулы Выручка/ Количество Excel вычислит общую сумму по полю Выручка, общую сумму по полю Количество и поделит первое значение на второе. В большинстве случаев это именно то, что нужно. Чтобы добавить вычисляемое поле с помощью VBA, воспользуйтесь метоY дом Add объекта CalculatedFields. Метод Add имеет два параметра: имя вычисляемого поля (Name) и его формула (Formula). Обратите внимание, что Сводные таблицы Глава 12 329 при определении вычисляемого поля с именем СредняяЦена Excel автоматиY чески создаст поле сводной таблицы Сумма по полю СредняяЦена (Sum of СредняяЦена), что выглядит весьма нелепо. Устранить недоразумение позвоY ляет свойство Name поля сводной таблицы. Также следует отметить, что имя поля сводной таблицы должно отличаться от имени соответствующего вычисY ляемого поля. Ниже приведен полный код макроса, создающего отчет о прибыльности товаров. Sub AccountingReport() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT WSD.Range("J1:M1").EntireColumn.Clear ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Товар", ColumnFields:="Данные" ' Определить вычисляемые поля. PT.CalculatedFields.Add Name:="СредняяЦена", _ Formula:="=Выручка/Количество" PT.CalculatedFields.Add Name:="СредняяСебестоимость ", _ Formula:="=Себестоимость/Количество" PT.CalculatedFields.Add Name:="ВаловаяПрибыль_%", _ Formula:="=Прибыль/Выручка" ' Определить поля области данных. With PT.PivotFields("Количество") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0" .Name = "Общее количество" End With With PT.PivotFields("Выручка") 330 Часть II Автоматизация Excel .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0" .Name = "Общая выручка" End With With PT.PivotFields("СредняяЦена") .Orientation = xlDataField .Function = xlSum .Position = 3 .NumberFormat = "#,##0.00" .Name = "Средняя цена" End With With PT.PivotFields("Себестоимость") .Orientation = xlDataField .Function = xlSum .Position = 4 .NumberFormat = "#,##0" .Name = "Общая себестоимость" End With With PT.PivotFields("СредняяСебестоимость ") .Orientation = xlDataField .Function = xlSum .Position = 5 .NumberFormat = "#,##0.00" .Name = "Средняя себестоимость" End With With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 6 .NumberFormat = "#,##0" .Name = "Валовая прибыль" End With With PT.PivotFields("ВаловаяПрибыль_%") .Orientation = xlDataField .Function = xlSum .Position = 7 .NumberFormat = "#0.0%" .Name = "Валовая прибыль, %" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub Результат выполнения макроса AccountingReport показан на рис. 12.26. Сводные таблицы Глава 12 331 Рис. 12.26. Полученный отчет позволяет составить мнение о прибыльности товаров “Подводные камни” вычисляемых элементов В отличие от вычисляемых полей, вычисляемые элементы сводной таблицы редко используются на практике. Главная особенность вычисляемого элемента состоит в возможности его добавления к существующему полю сводной таблицы. Рассмотрим пример использования вычисляемых элементов. ПредполоY жим, что одно подразделение компании занимается продажами товаров ABC и DEF, а другое подразделение YYYY продажами товара XYZ. Добавим к полю Товар элемент, вычисляющий сумму содержимого элементов ABC и DEF. Sub CalculatedItemsAreEvil() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Dim FinalRow As Long Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT WSD.Range("J1:M1").EntireColumn.Clear ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Товар", ColumnFields:="Данные" ' Создать вычисляемый элемент в поле "Товар". PT.PivotFields("Товар").CalculatedItems.Add _ "Подразделение1", "=ABC+DEF" ' Переместить вычисляемый элемент на 3-ю позицию. PT.PivotFields("Товар").PivotItems( _ "Подразделение1").Position = 3 With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0" 332 Часть II Автоматизация Excel .Name = "Общая выручка" End With With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0" .Name = "Валовая прибыль" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub Результат выполнения макроса CalculatedItemsAreEvil показан на рис. 12.27. Проанализируем содержимое поля Общая выручка полученной сводной таблицы. Подсчет значения вычисляемого элемента вполне корректен: 46 млн (товар ABC) плюс 47 млн (товар DEF) примерно равняются 93 млн. Однако итоговое значение общей выручки должно равняться 146 млн (93 млн плюс 53 млн), но никак не 241 млн! Очевидно, что при подсчете строки Общий итог (Grand Total) Excel не делает различие между обычными и вычисляемыY ми элементами. Единственный способ исправить положение заключается в сокрытии строк, соответствующих товарам ABC и DEF: With PT.PivotFields("Товар") .PivotItems("ABC").Visible = False .PivotItems("DEF").Visible = False End With Результат выполнения измененного макроса показан на рис. 12.28. Рис. 12.27. Использование вычисляемых эле& ментов чревато весьма неприятными послед& ствиями Рис. 12.28. После сокрытия строк, соответст& вующих товарам ABC и DEF, сводная таблица вновь содержит корректные сведения Суммирование значений полей области данных сводной таблицы путем группирования Сводная таблица, показанная на рис. 12.29, содержит огромное число строк — по одной на каждый день отгрузки товара. Сводные таблицы Глава 12 333 Рис. 12.29. До группирования сводная таблица со& держит огромное число строк — по одной на каждый день отгрузки товара В большинстве случаев такая детализация отчета избыточна YYYY гораздо боY лее удобно анализировать итоговые объемы продаж за определенный промеY жуток времени (например, месяц или квартал). К счастью, Excel поддерживает группирование дат в сводной таблице. Это гораздо эффективнее использования загадочной формулы =A2+1-ДЕНЬ(A2) (=A2+1-DAY(A2)), преобразующей произвольную дату в дату, соответствуюY щую первому дню исходного месяца и года. Чтобы сгруппировать даты с помощью пользовательского интерфейса ExY cel, выделите любую ячейку в столбце ДатаОтгрузки и выберите команду Группа и структура Группировать (Group and Show Detail) из раскрываюY щегося списка Сводная таблица (PivotTable) панели инструментов Сводные таблицы (PivotTable). В открывшемся диалоговом окне Группирование (Grouping) выберите значения Месяцы (Months), Кварталы (Quarters), Годы (Years) и щелкните на кнопке ОК (рис. 12.30). Перед выполнением указанных выше действий сводная таблица содержала поле ДатаОтгрузки с группированием дат по дням. После изменения спосоY ба группирования дат сводная таблица также содержит поле ДатаОтгрузки, однако теперь даты в этом поле сгруппированы по месяцам. Кроме того, в сводную таблицу было добавлено два новых поля YYYY Годы (Years) и Кварталы 334 Часть II Автоматизация Excel (Quarters). При группировании дат поле с наибольшей детализацией всегда перенимает имя исходного поля, а остальные поля добавляются к сводной таблице по мере необходимости. Рис. 12.30. Диалоговое окно Группирование позволя& ет выбрать шаг группирования дат в сводной таблице Результат группирования дат сводной таблицы по месяцам, кварталам и годам показан на рис. 12.31. Рис. 12.31. Сводные таблицы Excel поддерживают группирование дат по месяцам, кварталам и годам Сводные таблицы Глава 12 335 Чтобы выполнить аналогичное действие в VBA, следует воспользоваться методом Group. Метод Group должен быть применен к диапазону, состоящеY му из одной ячейки, YYYY заголовка столбца ДатаОтгрузки или любой его ячейки с датой. Обратите внимание, что впервые в этой главе Excel позволяетY ся пересчитать промежуточную сводную таблицу. Прежде всего, создадим сводную таблицу с полем ДатаОтгрузки в обласY ти столбцов и пересчитаем ее (это необходимо для заполнения поля ДатаОтгрузки данными). При группировании дат обратимся к свойству LabelRange, возвращающему заголовок соответствующего поля сводной таблицы. Sub ReportByMonth() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="ДатаОтгрузки", ColumnFields:="Регион" With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0" .Name = "Общая выручка" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Сгруппировать содержимое столбца "ДатаОтгрузки" ' по месяцам, кварталам и годам. PT.PivotFields("ДатаОтгрузки").LabelRange.Group _ Start:=True, End:=True, Periods:=Array(False, False, False, _ 336 Часть II Автоматизация Excel False, True, True, True) ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub Группирование дат по неделям Excel позволяет группировать даты по дням, месяцам, кварталам, годам, но не по неделям. Этот недочет легко исправить путем определения единицы группирования, состоящей из 7 дней. По умолчанию Excel начинает группирование с первой даты исходных данных, в рассматриваемом случае YYYY с четверга 1 января 2004 года. Чтобы наY чать группирование с понедельника, следует воспользоваться параметром Start метода Group. Функция Weekday поможет определить смещение перY вой даты исходных данных от начала недели. Группирование по неделям имеет один существенный недостаток YYYY оно исключает возможность какогоYлибо иного группирования. Другими словами, Excel не разрешает группировать даты по неделям и, к примеру, кварталам. Ниже приведен код, реализующий группирование дат сводной таблицы по неделям. Sub ReportByWeek() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="ДатаОтгрузки", ColumnFields:="Регион" With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 Сводные таблицы Глава 12 337 .NumberFormat = "#,##0" .Name = "Общая выручка" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Сгруппировать содержимое столбца "ДатаОтгрузки" по неделям. ' Определить дату понедельника 1-й недели. FirstDate = PT.PivotFields("ДатаОтгрузки").LabelRange.Offset( _ 1, 0).Value WhichDay = Application.WorksheetFunction.Weekday(FirstDate, 3) StartDate = FirstDate - WhichDay PT.PivotFields("ДатаОтгрузки").LabelRange.Group _ Start:=StartDate, End:=True, By:=7, _ Periods:=Array(False, False, False, True, False, _ False, False) ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub Результат выполнения макроса ReportByWeek показан на рис. 12.32. Рис. 12.32. При необходимости даты сводной таблицы можно сгруппировать по неделям 338 Часть II Автоматизация Excel Определение сроков выполнения заказов Как упоминалось выше, при группировании дат поле с наибольшей детаY лизацией всегда перенимает имя исходного поля, а остальные поля (например, Годы (Years) или Кварталы (Quarters)) добавляются к сводной таблице по мере необходимости. Предположим, что предприятию необходимо определить сроки выполнеY ния заказов. Полный цикл производства товара включает 12Yнедельную заY держку, связанную с поставкой необходимых компонентов. В идеальной сиY туации на выполнение заказа отводится не менее 13 недель. В противном слуY чае следует реализовать систему прогнозирования поступления заказов. Добавив к исходным данным поле ДатаЗаказа, создадим сводную таблиY цу, показывающую поступление выручки от выполнения заказов по месяцам. 1. Создайте сводную таблицу с полем ДатаОтгрузки в области строк и полем Выручка в области данных. Сгруппируйте поле ДатаОтгрузки по месяцам и годам, в результате чего Excel создаст два новых поля YYYY ДатаОтгрузки и Годы (Years). 2. Поместите поля ДатаОтгрузки и Годы в область столбцов сводной таблицы. 3. Добавьте поле ДатаЗаказа к области строк сводной таблицы. 4. Сгруппируйте поле ДатаЗаказа по месяцам и годам, в результате чего Excel создаст два новых поля YYYY ДатаЗаказа и Годы2 (Years2). Следует отметить, что новые версии Excel корректно справляются с групY пировкой дат в нескольких полях сводной таблицы, подтверждением чего моY жет служить факт создания поля Годы2 вместо поля Годы. Ниже приведен полный код макроса, создающего отчет о поступлении выY ручки от выполнения заказов по месяцам. Sub MeasureLeadtime() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные (2)") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Сводные таблицы Глава 12 339 Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("K2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк. PT.AddFields RowFields:="ДатаОтгрузки" With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0" .Name = "Общая выручка" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Сгруппировать содержимое поля "ДатаОтгрузки" по месяцам и годам. PT.PivotFields("ДатаОтгрузки").LabelRange.Group _ Start:=True, End:=True, Periods:=Array(False, False, False, _ False, True, False, True) ' Поместить поля "Годы" и "ДатаОтгрузки" в область столбцов. With PT.PivotFields("Годы") .Orientation = xlColumnField .Position = 1 End With With PT.PivotFields("ДатаОтгрузки") .Orientation = xlColumnField .Position = 2 End With ' Добавить поле "ДатаЗаказа" в область строк. With PT.PivotFields("ДатаЗаказа") .Orientation = xlRowField .Position = 1 End With ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Сгруппировать содержимое поля "ДатаЗаказа" по месяцам и годам. PT.PivotFields("ДатаЗаказа").LabelRange.Group _ Start:=True, End:=True, Periods:=Array(False, False, False, _ False, True, False, True) ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True End Sub 340 Часть II Автоматизация Excel Результат выполнения макроса MeasureLeadtime показан на рис. 12.33. Рис. 12.33. Данный отчет свидетельствует о том, что пред& приятие нуждается в гибкой системе прогнозирования по& ступления заказов Дополнительные возможности сводных таблиц Зачастую даже опытные пользователи Excel не знакомы со всеми возможY ностями сводных таблиц. Восполним же этот пробел! Отображение лучшей десятки заказчиков Согласно широко известному правилу 80/20, 80% дохода компании приноY сят 20% ее клиентов. В реальных условиях это не так уж далеко от истины. Создавая отчет для руководства компании, разумно включить в него данY ные о 5YY10 лучших заказчиках. Чтобы отобразить заданное число наибольших (наименьших) элементов поY ля сводной таблицы с помощью пользовательского интерфейса Excel, щелкните на кнопке Дополнительно (Advanced) диалогового окна Вычисление поля сводной таблицы (PivotTable Field). Установите переключатель Включено (On), выберите из раскрывающегося списка Отображать (Show) значение Наибольших (Top), установите значение расположенного справа от списка Отображать счетчика равным 6 и выберите из раскрывающегося списка Сводные таблицы Глава 12 341 С помощью поля (Using field) значение Сумма по полю Выручка (Sum of ВыY ручка), как показано на рис. 12.34. Рис. 12.34. Функция Автоотображение лучшей десятки (Top 10 AutoShow) позволяет отобразить заданное число наи& больших (наименьших) элементов поля сводной таблицы На заметку Название функции Автоотображение лучшей десятки (Top 10 AutoShow) может ввести в заблуждение. На самом деле, Excel позволяет отобразить заданное число (от 1 до 500) как наибольших, так и наименьших значений поля сводной таблицы. Чтобы отобразить 6 наибольших элементов поля Заказчик с расчетом по полю Общая выручка в VBA, воспользуйтесь методом AutoShow: ' Вывести 6 лучших заказчиков. PT.PivotFields("Заказчик").AutoShow Type:=xlAutomatic, _ Range:=xlTop, Count:=6, Field:="Общая выручка" Вызвав метод AutoShow и пересчитав сводную таблицу, рекомендуется скопировать полученный отчет, после чего вернуться к исходной сводной таблице, чтобы подсчитать значение поля Общая выручка для всех заказY чиков. В приведенном ниже коде это достигается путем удаления поля Заказчик из сводной таблицы, ее пересчета и копирования в отчет полученY ной итоговой строки. Sub Top6CEOReport() ' Этот макрос создает отчет о 6 лучших заказчиках ' с полями "Общая прибыль", "Валовая прибыль" и ' "Валовая прибыль, %". Dim WSD As Worksheet Dim WSR As Worksheet Dim WBN As Workbook Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") 342 Часть II Автоматизация Excel ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Заказчик", ColumnFields:="Данные" ' Определить вычисляемые поля. PT.CalculatedFields.Add Name:="ВаловаяПрибыль_%", _ Formula:="=Прибыль/Выручка" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" .Name = "Общая выручка" End With With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0,K" .Name = "Валовая прибыль" End With With PT.PivotFields("ВаловаяПрибыль_%") .Orientation = xlDataField .Function = xlSum .Position = 3 .NumberFormat = "#0.0%" .Name = "Валовая прибыль, %" End With ' Отсортировать поле "Заказчик" по убыванию, ' используя значение поля "Общая выручка". PT.PivotFields("Заказчик").AutoSort Order:=xlDescending, _ Field:="Общая выручка" ' Отобразить 6 наибольших значений поля "Заказчик" ' с расчетом по полю "Общая выручка". PT.PivotFields("Заказчик").AutoShow Type:=xlAutomatic, _ Range:=xlTop, Count:=6, Field:="Общая выручка" Сводные таблицы Глава 12 343 ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Создать новую рабочую книгу ' с одним рабочим листом. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSR = WBN.Worksheets(1) WSR.Name = "Отчет" ' Создать заголовок отчета. With WSR.[A1] .Value = "6 лучших заказчиков" .Font.Size = 14 End With ' Скопировать диапазон ячеек TableRange2.Offset(1, 0) ' в рабочий лист "Отчет" новой рабочей книги. PT.TableRange2.Offset(1, 0).Copy WSR.[A3].PasteSpecial Paste:=xlPasteValuesAndNumberFormats LastRow = WSR.Cells(65536, 1).End(xlUp).Row WSR.Cells(LastRow, 1).Value = "Всего (по 6 заказчикам)" ' Подсчитать значение поля "Общая выручка" для всех заказчиков. PT.PivotFields("Заказчик").Orientation = xlHidden PT.ManualUpdate = False PT.ManualUpdate = True PT.TableRange2.Offset(2, 0).Copy WSR.Cells(LastRow + 2, 1).PasteSpecial Paste:= _ xlPasteValuesAndNumberFormats WSR.Cells(LastRow + 2, 1).Value = "Всего (по всем заказчикам)" ' Удалить сводную таблицу и объект PivotCache. PT.TableRange2.Clear Set PTCache = Nothing ' Применить стилевое форматирование: ' - выделить заголовки столбцов полужирным шрифтом; ' - выровнять заголовки столбцов по правому краю; ' - выполнить автоподбор ширины столбцов отчета. Range("A3").EntireRow.Font.Bold = True Range("A3").EntireRow.HorizontalAlignment = xlRight Range("A3").HorizontalAlignment = xlLeft Range("B3").Value = "Общая выручка" WSR.Range(WSR.Range("A2"), WSR.Cells(LastRow + 2, 4)).Columns.AutoFit Range("A2").Select MsgBox "Отчет для руководства компании успешно создан" End Sub Результат выполнения макроса Top6CEOReport показан на рис. 12.35. 344 Часть II Автоматизация Excel Рис. 12.35. Отчет о 6 лучших заказчиках компании создан на основе двух сводных таблиц Обратите внимание, что отчет о 6 лучших заказчиках компании был создан на основе двух сводных таблиц YYYY таблицы с полем Заказчик в области строк, которая позволила получить список 6 лучших заказчиков, и таблицы без полей в области строк, которая использовалась для подсчета итоговой выручки. Использование сводной таблицы для фильтрации исходных данных Проведем небольшой эксперимент. Дважды щелкните на любом числовом значении сводной таблицы, в результате чего Excel создаст новый рабочий лист и скопирует в него все записи исходных данных, на основании которых было полуY чено это числовое значение. По существу, эта особенность сводных таблиц предY ставляет собой простейший способ выполнения запросов к исходным данным. Чтобы добиться аналогичного результата в VBA, следует установить значеY ние свойства диапазона ячеек ShowDetail равным True, как показано ниже: PT.TableRange2.Offset(2, 1).Resize(1, 1).ShowDetail = True Следующий макрос создает сводную таблицу, содержащую сведения об общей выручке, приходящейся на трех лучших заказчиков. Подробная инY формация обо всех сделках каждого заказчика выводится на отдельном рабоY чем листе. Похожая задача рассматривалась в практикуме ‘‘Использование двух расширенных фильтров для создания отчетов по каждому заказчику’’ на с. 293, где для ее решения предлагалось использовать расширенный фильтр. Sub RetrieveTop3CustomerDetail() Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT Сводные таблицы Глава 12 345 ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Заказчик", ColumnFields:="Данные" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" .Name = "Общая выручка" End With ' Отсортировать поле "Заказчик" по убыванию, ' используя значение поля "Общая выручка". PT.PivotFields("Заказчик").AutoSort Order:=xlDescending, _ Field:="Общая выручка" ' Отобразить 3 наибольших значения поля "Заказчик" ' с расчетом по полю "Общая выручка". PT.PivotFields("Заказчик").AutoShow Type:=xlAutomatic, _ Range:=xlTop, Count:=3, Field:="Общая выручка" ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Создать отчеты о сделках каждого заказчика. For i = 1 To 3 PT.TableRange2.Offset(i + 1, 1).Resize(1, 1).ShowDetail = True Range("A1:A2").EntireRow.Insert Range("A1").Value = "Информация о сделках с заказчиком " _ & PT.TableRange2.Offset(i + 1, 0).Resize(1, 1).Value & " _ (порядковый номер в списке лучших заказчиков: " & i & ")" Next i MsgBox "Отчеты о сделках с 3-мя лучшими заказчиками _ успешно созданы" End Sub 346 Часть II Автоматизация Excel Отчет о сделках самого крупного заказчика, полученный в результате выY полнения макроса RetrieveTop3CustomerDetail, показан на рис. 12.36. Рис. 12.36. В результате выполнения макроса была создана сводная таблица, со& держащая сведения о 3&х лучших заказчиках, а также подробные отчеты о сделках каждого из заказчиков Использование полей области страницы сводной таблицы В добавок к полям в области строк, области столбцов и области данных, сводная таблица может содержать одно или несколько полей в области страY ницы. Поля области страницы выводятся в строках, расположенных над отчеY том сводной таблицы. Они могут использоваться для фильтрации отчета по различным критериям, например, по товару, региону или комбинации товара и региона. На рис. 12.37 показана сводная таблица, отображающая 10 лучших заказчиков товара ABC в западном регионе. Чтобы определить поле области страницы в VBA, воспользуйтесь параметY ром PageFields метода AddFields. Следующий код определяет сводную таблицу с полем Регион в области страницы: PT.AddFields RowFields:="Заказчик", ColumnFields:="Данные", _ PageFields:="Регион" По умолчанию значение поля области страницы Регион устанавливается равным (Все) ((All)). Чтобы ограничиться данными только по какомуYлибо определенному региону (например, западному), воспользуйтесь свойством CurrentPage объекта PivotField: PT.PivotFields("Регион").CurrentPage = "Запад" Сводные таблицы Глава 12 347 Рис. 12.37. Область страницы сводной таблицы содержит по& ля Регион и Товар, которые могут использоваться для фильтрации отчета по товару, региону или их комбинации Поля области страницы часто применяются при создании пользовательY ской формы, позволяющей выбрать требуемый регион или товар. Присвоив выбранное значение свойству CurrentPage, можно быстро получить требуеY мый отчет. Другое применение поля области страницы заключается в создании отчеY тов для всех значений этого поля. К примеру, чтобы определить общее число регионов, воспользуйтесь свойством Count объекта PivotItems: PT.PivotFields("Регион").PivotItems.Count Ниже приведено два равноценных цикла по всем значениям поля Регион: For i = 1 To PT.PivotFields("Регион").PivotItems.Count PT.PivotFields("Регион").CurrentPage = _ PT.PivotFields("Регион").PivotItems(i).Name PT.ManualUpdate = False PT.ManualUpdate = True Next i For Each PivItem In PT.PivotFields("Регион").PivotItems PT.PivotFields("Регион").CurrentPage = PivItem.Name PT.ManualUpdate = False PT.ManualUpdate = True Next PivItem Конечно же, поочередный вывод отчетов на экран имеет немного практиY ческого смысла. Обычно отчет создается для его последующего сохранения. Ранее в этой главе для копирования содержимого сводной таблицы приY менялось свойство TableRange2 объекта PivotTable. Свойство TableRange2 ссылается на все строки сводной таблицы, включая строки полей обY ласти страницы. Чтобы исключить строки полей области страницы, воспольY зуйтесь свойством TableRange1 объекта PivotTable. Следующие строки ссылаются на одинаковый диапазон ячеек рабочего листа, показанного на рис. 12.37: 348 Часть II Автоматизация Excel PT.TableRange2.Offset(4, 0) PT.TableRange1.Offset(1, 0) Выбор того или иного свойства YYYY дело личного предпочтения. Тем не меY нее, свойство TableRange2 имеет некоторое преимущество, так как вызов метода TableRange2.Clear позволяет удалить сводную таблицу с рабочего листа. А вот попытка вызова метода TableRange1.Clear неминуемо заверY шится выводом сообщения об ошибке времени выполнения 1004, напомиY нающего о невозможности перемещения или изменения части сводной табY лицы Excel. Следующий макрос создает отчеты о 10 лучших заказчиках в каждом из регионов. Каждый отчет помещается в новую рабочую книгу. Sub Top10ByRegionReport() ' Этот макрос создает отчет о 10 лучших ' заказчиках в каждом из регионов. Dim WSD As Worksheet Dim WSR As Worksheet Dim WBN As Workbook Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк, области ' столбцов и области страницы. PT.AddFields RowFields:="Заказчик", ColumnFields:="Данные", _ PageFields:="Регион" ' Определить вычисляемые поля. PT.CalculatedFields.Add Name:="ВаловаяПрибыль_%", _ Formula:="=Прибыль/Выручка" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" Сводные таблицы Глава 12 349 .Name = "Общая выручка" End With With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0,K" .Name = "Валовая прибыль" End With With PT.PivotFields("ВаловаяПрибыль_%") .Orientation = xlDataField .Function = xlSum .Position = 3 .NumberFormat = "#0.0%" .Name = "Валовая прибыль, %" End With ' Отсортировать поле "Заказчик" по убыванию, ' используя значение поля "Общая выручка". PT.PivotFields("Заказчик").AutoSort Order:=xlDescending, _ Field:="Общая выручка" ' Отобразить 10 наибольших значений поля "Заказчик" ' с расчетом по полю "Общая выручка". PT.PivotFields("Заказчик").AutoShow Type:=xlAutomatic, _ Range:=xlTop, Count:=10, Field:="Общая выручка" ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True Ctr = 0 ' Создать цикл по всем значениям поля "Регион". For Each PivItem In PT.PivotFields("Регион").PivotItems Ctr = Ctr + 1 PT.PivotFields("Регион").CurrentPage = PivItem.Name PT.ManualUpdate = False PT.ManualUpdate = True ' Создать новую рабочую книгу с одним рабочим листом. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSR = WBN.Worksheets(1) WSR.Name = PivItem.Name ' Создать заголовок отчета. With WSR.[A1] .Value = "10 лучших заказчиков в регионе " & _ PivItem.Name .Font.Size = 14 End With ' Скопировать диапазон ячеек TableRange2.Offset(3, 0) ' в рабочий лист новой рабочей книги. PT.TableRange2.Offset(3, 0).Copy WSR.[A3].PasteSpecial Paste:=xlPasteValuesAndNumberFormats 350 Часть II Автоматизация Excel LastRow = WSR.Cells(65536, 1).End(xlUp).Row WSR.Cells(LastRow, 1).Value = "Всего ( _ по 10 лучшим заказчикам)" ' Применить стилевое форматирование: ' - выделить заголовки столбцов полужирным шрифтом; ' - выровнять заголовки столбцов по правому краю; ' - выполнить автоподбор ширины столбцов отчета. Range("A3").EntireRow.Font.Bold = True Range("A3").EntireRow.HorizontalAlignment = xlRight Range("A3").HorizontalAlignment = xlLeft Range("B3").Value = "Выручка" WSR.Range(WSR.Range("A3"), WSR.Cells( _ LastRow, 4)).Columns.AutoFit Range("A2").Select Next PivItem ' Удалить сводную таблицу и объект PivotCache. PT.TableRange2.Clear Set PTCache = Nothing MsgBox Ctr & " отчета о лучших заказчиках в регионах _ успешно созданы" End Sub Отчеты, созданные в результате выполнения макроса Top10ByRegionReport, показаны на рис. 12.38. Рис. 12.38. Отчеты о 10 лучших заказчиках в каждом регионе созданы с помощью цикла по всем значениям поля Регион Сводные таблицы Глава 12 351 Фильтрация элементов полей сводной таблицы вручную Вдобавок к созданию вычисляемых элементов, Excel позволяет фильтроY вать элементы полей сводной таблицы вручную. Рассмотрим следующую задачу. К примеру, некий продавец обуви желает получить отчет о продажах сандалий в регионах с теплым климатом. СледуюY щая строка кода позволяет скрыть определенный элемент поля Магазин: PT.PivotFields("Магазин").PivotItems("Миннеаполис").Visible = False Присвоение значения False свойству Visible всех элементов поля приY ведет к возникновению ошибки времени выполнения. К примеру, на 1Yй итеY рации цикла макрос может отобразить товары A и B, а на 2Yй итерации YYYY тоY вары C и D. Если скрыть товары A и B до того, как будут отображены товаY ры C и D, возникнет ошибка. Чтобы избежать подобного недоразумения, присвойте значение True свойству Visible всех элементов поля перед его фильтрацией. Создадим отчет, включающий информацию о выручке, прибыли и валовой прибыли (%) от продаж товаров ABC и DEF по регионам. Чтобы создать такой отчет с помощью пользовательского интерфейса Excel, необходимо выполнить следующие действия. 1. Создать сводную таблицу с полем Регион в области строк, полем Товар в области столбцов и полем Выручка в области данных. 2. Вручную отфильтровать поле Товар, скрыв все элементы за исключеY нием ABC и DEF. 3. Переместить поле Товар из области столбцов в область страницы. 4. Поместить поля Прибыль и ВаловаяПрибыль_% (вычисляемое поле) в область данных. Сводная таблица, полученная в результате выполнения указанных выше действий, показана на рис. 12.39. Рис. 12.39. Элемент XYZ поля Товар скрыт, однако об этом нет никаких уведомлений Узнать о том, что элемент XYZ поля Товар скрыт, позволяет лишь раскрыY вающийся список, расположенный справа от названия поля Товар (рис. 12.40). Создание подобного отчета рекомендуется автоматизировать с помощью VBA. Это позволит не только добавить заголовок и стилевое форматирование, 352 Часть II Автоматизация Excel но и поместить поле Товар непосредственно в область страницы, отфильтроY вав нужные элементы с помощью свойства Visible. Рис. 12.40. Факт фильтрации поля Товар обнаруживается лишь при раскрытии соответствующего списка Ниже приведен полный код макроса, создающего отчет о продажах товаров ABC и DEF по регионам. Sub ProduceReportOfTwoProducts() ' Этот макрос создает отчет о продажах ' товаров ABC и DEF в регионах. Dim WSD As Worksheet Dim WSR As Worksheet Dim WBN As Workbook Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Регион", ColumnFields:=Array( _ "Данные", "Товар") ' Определить вычисляемые поля. PT.CalculatedFields.Add Name:="ВаловаяПрибыль_%", _ Сводные таблицы Formula:="=Прибыль/Выручка" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" .Name = "Общая выручка" End With ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True ' Убедиться, что поле "Товар" не содержит скрытых элементов. For Each PivItem In PT.PivotFields("Товар").PivotItems PivItem.Visible = True Next PivItem ' Отфильтровать поле "Товар". For Each PivItem In PT.PivotFields("Товар").PivotItems Select Case PivItem.Name Case "ABC", "DEF" PivItem.Visible = True Case Else PivItem.Visible = False End Select Next PivItem ' Переместить поле "Товар" в область страницы. PT.PivotFields("Товар").Orientation = xlPageField ' Добавить оставшиеся поля области данных. With PT.PivotFields("Прибыль") .Orientation = xlDataField .Function = xlSum .Position = 2 .NumberFormat = "#,##0,K" .Name = "Валовая прибыль" End With With PT.PivotFields("ВаловаяПрибыль_%") .Orientation = xlDataField .Function = xlSum .Position = 3 .NumberFormat = "#0.0%" .Name = "Валовая прибыль, %" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True Ctr = 0 Глава 12 353 354 Часть II Автоматизация Excel ' Создать новую рабочую книгу с одним рабочим листом. Set WBN = Workbooks.Add(xlWBATWorksheet) Set WSR = WBN.Worksheets(1) WSR.Name = "Отчет" ' Создать заголовок отчета. With WSR.[A1] .Value = "Продажи по регионам - только товары ABC и DEF" .Font.Size = 14 End With ' Скопировать диапазон ячеек TableRange2.Offset(3, 0) ' в рабочий лист новой рабочей книги. PT.TableRange2.Offset(3, 0).Copy WSR.[A3].PasteSpecial Paste:=xlPasteValuesAndNumberFormats LastRow = WSR.Cells(65536, 1).End(xlUp).Row WSR.Cells(LastRow, 1).Value = "Всего (по товарам ABC и DEF)" ' Применить стилевое форматирование: ' - выделить заголовки столбцов полужирным шрифтом; ' - выровнять заголовки столбцов по правому краю; ' - выполнить автоподбор ширины столбцов отчета. Range("A3").EntireRow.Font.Bold = True Range("A3").EntireRow.HorizontalAlignment = xlRight Range("A3").HorizontalAlignment = xlLeft Range("B3").Value = "Выручка" WSR.Range(WSR.Range("A3"), WSR.Cells(LastRow, _ 4)).Columns.AutoFit Range("A2").Select ' Удалить сводную таблицу и объект PivotCache. PT.TableRange2.Clear Set PTCache = Nothing MsgBox "Отчет о продажах товаров ABC и DEF успешно создан" End Sub Результат выполнения макроса ProduceReportOfTwoProducts показан на рис. 12.41. Рис. 12.41. VBA позволяет гарантировать отсутствие ошибки времени выполнения и упростить создание отчета о продажах товаров ABC и DEF по регионам Сводные таблицы Глава 12 355 Сумма, среднее, количество, минимум, максимум и др. Единственной статистикой, подсчитываемой для поля сводной таблицы ранее в этой главе, была сумма значений элементов поля. Помимо суммы, ExY cel позволяет вычислить среднее значение всех элементов поля, минимальное, максимальное значения и т.п. Чтобы подсчитать требуемую статистику в VBA, создайте поле области данных с уникальным именем и установите соответстY вующее значение свойства xlFunction. Следующий макрос демонстрирует пример вычисления 5Yти различных статистик для поля Выручка по каждому заказчику. Sub ReportManyDetailsByCustomer() ' Макрос, вычисляющий общую сумму выручки, количество ' заказов, среднюю выручку, минимальный и максимальный ' заказ для каждого клиента. Dim WSD As Worksheet Dim PTCache As PivotCache Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных и создать ' объект кэша сводных таблиц. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) Set PTCache = ActiveWorkbook.PivotCaches.Add( _ SourceType:=xlDatabase, SourceData:=PRange.Address) Set PT = PTCache.CreatePivotTable(TableDestination:= _ WSD.Range("J2"), TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Заказчик", ColumnFields:="Данные" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" .Name = "Общая выручка" End With With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlCount .Position = 2 356 Часть II Автоматизация Excel .NumberFormat = "#,##0" .Name = "Кол-во заказов" End With With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlAverage .Position = 3 .NumberFormat = "#,##0" .Name = "Средняя выручка" End With With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlMin .Position = 4 .NumberFormat = "#,##0" .Name = "Минимальный заказ" End With With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlMax .Position = 5 .NumberFormat = "#,##0" .Name = "Максимальный заказ" End With ' Заполнить нулями пустые ячейки в области данных. PT.NullString = "0" ' Пересчитать сводную таблицу. PT.ManualUpdate = False PT.ManualUpdate = True Ctr = 0 WSD.Select End Sub Результат выполнения макроса ReportManyDetailsByCustomer покаY зан на рис. 12.42. Дополнительные вычисления в полях области данных сводной таблицы Excel позволяет проводить так называемые дополнительные вычисления в полях области данных сводной таблицы. В частности, значения элементов поY ля можно отображать в виде доли от общей суммы, доли от суммы по строке, доли от суммы по столбцу или в виде разницы по отношению к значению другого элемента (рис. 12.43). Сводные таблицы Глава 12 357 Рис. 12.42. Каждое поле области данных сводной таблицы содержит различную статисти& ку поля Выручка (слева направо): общая сумма выручки, количество заказов, средняя сумма выручки, минимальная сумма заказа, максимальная сумма заказа Рис. 12.43. Excel предлагает разнообразные способы проведения дополнительных вычислений в полях области данных сводной таблицы 358 Часть II Автоматизация Excel В VBA вид дополнительного вычисления определяется с помощью свойстY ва Calculation объекта PivotField, которое может принимать следующие значения: xlPercentOf, xlPercentOfColumn, xlPercentOfRow, xlPercentOfTotal, xlRunningTotal, xlPercentDifferenceFrom, xlDifferenceFrom, xlIndex и xlNoAdditionalCalculation. Некоторые тиY пы вычислений требуют указания базового поля (свойство BaseField объекY та PivotField) или комбинации базового поля и базового элемента (свойство BaseItem объекта PivotField). Более подробно дополнительные вычисления в полях области данных сводной таблицы рассматриваются в слеY дующих разделах этой главы. Доля от общей суммы Чтобы отобразить значения элементов поля в виде доли от общей суммы по этому полю, установите значение свойства Calculation равным xlPercentOfTotal, как показано ниже: ' Подсчитать долю от общей суммы. With PT.PivotFields("Выручка") .Orientation = xlDataField .Caption = "Доля от общей суммы" .Function = xlSum .Position = 2 .NumberFormat = "#0.0%" .Calculation = xlPercentOfTotal End With Приведенное отличие от значения предыдущего элемента поля Сгруппировав даты поля ДатаОтгрузки по месяцам, можно составить отY чет о процентном изменении выручки относительно предыдущего месяца. Для этого установите значение параметра Calculation равным xlPercentDifferenceFrom, параметра BaseField — ДатаОтгрузки, а параY метра BaseItem — (назад) ((previous)). ' Подсчитать процентное отличие от предыдущего месяца. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Caption = "Отличие, %" .Calculation = xlPercentDifferenceFrom .BaseField = "ДатаОтгрузки" .BaseItem = "(назад)" .Position = 3 .NumberFormat = "#0.0%" End With Одно из наиболее существенных ограничений, касающееся вычислений, содержащих позиционные ссылки, заключается в невозможности использоY вания методов AutoSort и AutoShow. В частности, это не позволяет сравY Сводные таблицы Глава 12 359 нить общий объем закупок товаров заказчиками, предварительно отсортироY вав поле Заказчик по убыванию с помощью поля Общая выручка. Приведенное отличие от значения заданного элемента поля Предположим, что компания предлагает своим клиентам товары трех каY тегорий: аппаратное обеспечение, программное обеспечение и договора на обслуживание компьютерной техники. Менеджеры по продажам получили заY дание увеличить выручку от продажи программного обеспечения и заключеY ния договоров на обслуживание на 10% по сравнению с выручкой от продажи аппаратного обеспечения. Чтобы создать соответствующую сводную таблицу с помощью VBA, установите значение параметра Calculation равным xlPercentDifferenceFrom, как показано ниже: ' Подсчитать процентное отличие от выручки, полученной ' в результате продажи аппаратного обеспечения. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Caption = "Отличие (%) от продаж аппаратного обеспечения" .Calculation = xlPercentDifferenceFrom .BaseField = "Товар" .BaseItem = "Аппаратное обеспечение" .Position = 3 .NumberFormat = "#0.0%" End With Нарастающий итог Для вычисления нарастающего итога по полю сводной таблицы необходиY мо указать базовое поле. В рассматриваемом примере таким полем является поле области строк сводной таблицы ДатаОтгрузки. Таким образом, чтобы вычислить нарастающий итог по полю Выручка, необходимо установить знаY чение свойства BaseField равным ДатаОтгрузки, как показано ниже: ' Подсчитать нарастающий итог. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Caption = "Нарастающий итог" .Calculation = xlRunningTotal .Position = 4 .NumberFormat = "#,##0,K" .BaseField = "ДатаОтгрузки" End With На рис. 12.44 показана сводная таблица с тремя полями области данных, полученными в результате описанных выше дополнительных вычислений, а именно: вычисления доли от общей суммы, приведенного отличия от значеY ния предыдущего элемента поля и нарастающего итога. 360 Часть II Автоматизация Excel Рис. 12.44. Каждое из полей области данных сводной таблицы содержит различные вычисления на основе суммы по полю Выручка: собственно сумма по полю Выручка, доля от общей суммы по полю Выручка, при& веденное отличие от значения предыдущего элемента поля Общая выручка и нарастающий итог по полю Общая выручка Практикум Создание сводных таблиц в Excel 97 с помощью VBA Впервые сводные таблицы были представлены в Excel 95. В Excel 97 реализация сводных таблиц была значительно улучшена, а в Excel 2000 — кардинальным об& разом изменена за счет добавления объекта кэша сводных таблиц PivotCache. Несмотря на то что Microsoft официально прекратила поддержку Excel 97 несколь& ко лет назад, этим продуктом до сих пор пользуются все еще достаточно много компаний. Чтобы обеспечить совместимость кода VBA, созданного с помощью Ex& cel 2003, с Excel 97, его придется изрядно переработать. Для создания сводной таблицы в Excel 97 используется метод PivotTableWizard. Рассмотрим пример построения простой сводной таблицы, отображаю& щей выручку от продажи товаров по регионам. Sub PivotExcel97Compatible() ' Этот макрос полностью совместим с Excel 97. Dim WSD As Worksheet Dim PT As PivotTable Dim PRange As Range Set WSD = Worksheets("Данные") ' Удалить существующие сводные таблицы. For Each PT In WSD.PivotTables PT.TableRange2.Clear Next PT ' Задать диапазон исходных данных. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row Сводные таблицы Глава 12 361 Set PRange = WSD.Cells(1, 1).Resize(FinalRow, 8) ' Создать сводную таблицу с помощью метода PivotTableWizard. Set PT = WSD.PivotTableWizard(SourceType:=xlDatabase, _ SourceData:=PRange.Address, _ TableDestination:="R2C10", TableName:="PivotTable1") PT.ManualUpdate = True ' Определить поля области строк и области столбцов. PT.AddFields RowFields:="Регион", ColumnFields:="Товар" ' Определить поля области данных. With PT.PivotFields("Выручка") .Orientation = xlDataField .Function = xlSum .Position = 1 .NumberFormat = "#,##0,K" .Name = "Общая выручка" End With PT.ManualUpdate = False PT.ManualUpdate = True WSD.Select End Sub Следующий шаг Сводные таблицы YYYY чрезвычайно гибкое и мощное средство в арсенале пользователя Excel. В комбинации с VBA они предоставляют превосходный вычислительный механизм и основу для создания всевозможных отчетов. В следующей главе рассматриваются распространенные задачи, встречающиеY ся при работе с Excel, и их решения с помощью VBA, предлагаемые опытными программистами со всех уголков мира. Глава 13 Excel âñåìîãóùèé Первый принцип удачного проY граммиста состоит в том, чтобы ниY когда не писать один и тот же код дважды. Второй принцип удачного программиста требует создавать код для каждой рутинной операции. В настоящей главе рассматриваY ются макросы, присланные опытY ными пользователями Excel со всего мира. Эти программы не только помогут сэкономить время, но и прольют свет на новые спосоY бы решения наиболее распростраY ненных задач. Различные программисты приY держиваются различных подходов к созданию программного кода. НаY глядным подтверждением этого явY ляются приведенные в данной главе макросы. Расширение возможностей Excel с помощью VBA VBA позволяет раскрыть возможY ности Excel, недоступные посредстY вом пользовательского интерфейса. В следующих разделах рассматриваY ется использование VBA для реалиY зации условного форматирования с более чем тремя условиями, и расY ширенного фильтра с более чем двуY мя условиями. 13 Расширение возможностей Excel с помощью VBA.................. 363 Файловые операции .................. 365 Объединение и разделение рабочих книг .................................372 Работа с примечаниями .............376 Замечательные возможности Excel VBA ........................................ 381 VBA для профессионалов.......... 386 На закуску ..................................... 402 Следующий шаг........................... 405 364 Часть II Автоматизация Excel Условное форматирование с более чем тремя условиями Макрос Worksheet_Change любезно предоставлен Расселом Гауфом (Russell Hauf), проживающим в Бивертоне, штат Орегон, США. По сути, макрос Worksheet_Change дублирует функцию условного форY матирования Excel. Увеличение максимального числа условий достигается за счет наличия управляющего листа, содержащего 2 столбца YYYY допустимых значений ячеек (столбец A) и соответствующих индексов цвета заливки (столбец B). Private Sub Worksheet_Change(ByVal Target As Range) ' Этот макрос реализует условное форматирование ' с более чем 3-мя условиями. Dim rng As Range ' Исходный диапазон может содержать более чем 1 ячейку, ' поскольку он задается как пересечение диапазонов Target ' и D:D. Благодаря этому макрос будет выполняться корректно ' при удалении содержимого нескольких ячеек столбца D или ' вводе значений нескольких ячеек столбца D в виде массива. Set rng = Intersect(Target, Range("D:D")) If rng Is Nothing Then Exit Sub Else Dim cl As Range For Each cl In rng On Error Resume Next ' Если содержимое ячейки отсутствует ' в диапазоне rngColors, ее заливка удаляется. cl.Interior.ColorIndex = _ Application.WorksheetFunction.VLookup(cl.Value, _ ThisWorkbook.Sheets("УФ (управляющий лист)").Range("rngColors"), _ 2, False) If Err.Number <> 0 Then cl.Interior.ColorIndex = xlNone End If Next cl End If End Sub Расширенный фильтр с более чем двумя условиями Макрос MultiFilter любезно предоставлен Ричи Силлсом (Richie Sills), проживающим в Вустере, Англия. Ричи работает консультантом по налогам в аудиторской компании. Стандартный расширенный фильтр Excel позволяет задавать не более двух условий. Следующий макрос позволяет обойти это ограничение. Sub MultiFilter() ' Обратите внимание, что Ричи размещает комментарии после кода. ' Другими словами, комментарий на строке 20 относится к коду ' на строке 19. Dim rngTarget As Range, rng1 As Range, rng2 As Range, _ rngMyRange As Range Const Crit1 As String = "DEF, LLC" Excel всемогущий Глава 13 365 Const Crit2 As String = "FGH LTD." Const Crit3 As String = "QRS INC." ' Критерии расширенного фильтра. Application.ScreenUpdating = False With Worksheets("Расширенный фильтр") .Rows(1).Insert .Range("A1").Value = "dummy" ' Создать заголовок "dummy". Set rngTarget = .Range("A1:A" & .Cells(Rows.Count, _ 1).End(xlUp).Row) ' Задать исходный диапазон. rngTarget.AutoFilter Field:=1, Criteria1:=Crit1, _ Operator:=xlOr, Criteria2:=Crit2 ' Применить стандартный расширенный фильтр с 2-мя критериями. Set rng1 = .AutoFilter.Range.Offset(1, 0).Resize( _ .AutoFilter.Range.Rows.Count - 1).SpecialCells(xlCellTypeVisible) ' Создать диапазон ячеек, ссылающийся ' на результат применения расширенного фильтра. rngTarget.AutoFilter ' Вернуться к исходным данным. rngTarget.AutoFilter Field:=1, Criteria1:=Crit3 ' Применить стандартный расширенный фильтр с 3-м критерием. Set rng2 = .AutoFilter.Range.Offset(1, 0).Resize( _ .AutoFilter.Range.Rows.Count - 1).SpecialCells(xlCellTypeVisible) ' Создать диапазон ячеек, ссылающийся ' на результат применения расширенного фильтра. rngTarget.AutoFilter .Rows(1).Delete ' Удалить заголовок "dummy". Set rngMyRange = Union(rng1, rng2) ' Объединить диапазоны ячеек rng1 и rng2. rngMyRange.EntireRow.Copy Destination:=Worksheets( _ "РФ (результат)").Range("A2") ' Скопировать полученный результат в рабочий лист "РФ (результат)". End With Worksheets("РФ (результат)").Select Application.ScreenUpdating = True MsgBox "Фильтр успешно применен" End Sub Файловые операции Макросы, приведенные в следующих разделах, используются для выполY нения различных файловых операций. 366 Часть II Автоматизация Excel Поиск файлов Макрос Srch и функция BrowseForFolderShell любезно предоставлеY ны Натаном П. Оливером (Nathan P. Oliver), проживающим в Миннеаполисе, штат Миннесота, США. Натан занимает должности финансового консультанY та и разработчика приложений. Следующий макрос проводит поиск файлов, в имени которых встречается заданная строка, в указанной папке и ее подпапках. По результатам поиска создается список, в котором для каждого найденного файла выводится ссылка на файл, размер файла и дата его последнего изменения. Sub Srch() Dim i As Long, z As Long, ws As Worksheet, y As Variant Dim fLdr As String y = Application.InputBox("Пожалуйста, введите строку, _ встречающуюся в имени файла", "Ввод информации") If y = False And Not TypeName(y) = "String" Then Exit Sub Application.ScreenUpdating = False fLdr = BrowseForFolderShell With Application.FileSearch .NewSearch .LookIn = fLdr .SearchSubFolders = True .Filename = y Set ws = ThisWorkbook.Worksheets.Add(Sheets(1)) On Error GoTo 1 2: ws.Name = "Результат поиска файлов" On Error GoTo 0 If .Execute() > 0 Then For i = 1 To .FoundFiles.Count If Left$(.FoundFiles(i), 1) = Left$(fLdr, 1) Then If CBool(Len(Dir(.FoundFiles(i)))) Then z = z + 1 ws.Cells(z + 1, 1).Resize(, 3) = _ Array(Dir(.FoundFiles(i)), _ FileLen(.FoundFiles(i)) \ 1000, _ FileDateTime(.FoundFiles(i))) ws.Hyperlinks.Add Anchor:=Cells(z + 1, _ 1), Address:=.FoundFiles(i) End If End If Next i End If End With ActiveWindow.DisplayHeadings = False With ws With .[a1:c1 ] .Value = [{"Ссылка на файл","Размер (Kb)","Дата _ последнего изменения"}] .Font.Underline = xlUnderlineStyleSingle .EntireColumn.AutoFit .HorizontalAlignment = xlCenter End With Excel всемогущий Глава 13 367 .[d1:iv1 ].EntireColumn.Hidden = True Range(.[a65536 ].End(3)(2), _ .[a65536 ]).EntireRow.Hidden = True Range(.[a2 ], .[c65536 ]).Sort [a2 ], xlAscending, _ Header:=xlNo End With Application.ScreenUpdating = True Exit Sub 1: Application.DisplayAlerts = False Worksheets("Результат поиска файлов").Delete Application.DisplayAlerts = True GoTo 2 End Sub Function BrowseForFolderShell() As String Dim objShell As Object, objFolder As Object Set objShell = CreateObject("Shell.Application") ' Расскомментируйте следующую строку, чтобы начать ' обзор папок с рабочего стола Windows. 'Set objFolder =objShell.BrowseForFolder(0,"Пожалуйста, _ выберите папку", 0, 0) ' Укажите папку, с которой нужно начать обзор. Set objFolder = objShell.BrowseForFolder(0, "Пожалуйста, _ выберите папку", 0, "c:\") If (Not objFolder Is Nothing) Then On Error Resume Next If IsError(objFolder.Items.Item.Path) Then _ BrowseForFolderShell = CStr(objFolder): GoTo Here On Error GoTo 0 If Len(objFolder.Items.Item.Path) > 3 Then BrowseForFolderShell = objFolder.Items.Item.Path _ & Application.PathSeparator Else BrowseForFolderShell = objFolder.Items.Item.Path End If Else: Application.ScreenUpdating = True: End End If Here: Set objFolder = Nothing: Set objShell = Nothing End Function Удаление рабочей книги после определенной даты Макрос Workbook_Open любезно предоставлен Томом Уртисом (Tom UrY tis), проживающим в СанYФранциско, штат Калифорния, США. Том YYYY глава консалтинговой компании Atlas Programming Management, расположенной в районе Залива. Приведенный ниже макрос удаляет активную рабочую книгу после 31 декабря 2004 года. 368 Часть II Автоматизация Excel Внимание Файл рабочей книги не помещается в Корзину (Recycle Bin), а навсегда удаляется с компьютера. Sub Workbook_Open() If Date <= #12/31/2004# Then Exit Sub MsgBox "Сейчас рабочая книга будет удалена!" With ThisWorkbook .Saved = True .ChangeFileAccess xlReadOnly Kill .FullName .Close False End With End Sub Создание команды меню “Закрыть и удалить” Макросы Workbook_AddinInstall, Workbook_AddinnUninstall и CloseAndKill любезно предоставлены Томми Майлзом (Tommy Miles), проживающем в Хьюстоне, штат Техас, США. Одной из наиболее распространенных операций, выполняемых в Excel, являY ется открытие временного файла, копирование из него нужной информации и заY крытие этого файла. Подобные файлы ‘‘разового использования’’ имеют свойство накапливаться, захламляя пространство жесткого диска компьютера. Макрос Workbook_AddinInstall добавляет к меню Excel Файл (File) коY манду Закрыть и удалить (рис. 13.1), выполнение которой приводит к закрытию активной рабочей книги и удалению ее файла. Помимо этого, имя файла активY ной рабочей книги удаляется из списка ранее открывавшихся файлов. Рис. 13.1. Команда Закрыть и удалить использу& ется для закрытия активной рабочей книги и уда& ления ее файла Excel всемогущий Глава 13 369 Public Const CONTROLNAME As String = "Закрыть и у&далить" Sub Workbook_AddinInstall() Dim cmdControl As CommandBarButton On Error Resume Next Set cmdControl = Application.CommandBars(1).Controls("Файл").Controls(CONTROLNAME) If cmdControl Is Nothing Then Set cmdControl = Application.CommandBars(1).Controls("Файл").Controls.Add( _ Type:=msoControlButton, Before:=Application.CommandBars( _ 1).Controls("Файл").Controls("Сохранить").Index) With cmdControl .Caption = CONTROLNAME .FaceId = 67 .Style = msoButtonIconAndCaption .DescriptionText = "Закрыть активную рабочую книгу _ и удалить ее файл" .OnAction = "CloseAndKill" End With Set cmdControl = Nothing End If On Error GoTo 0 MsgBox "Команда ""Закрыть и удалить"" доступна из меню ""Файл""" End Sub Sub Workbook_AddinUninstall() On Error Resume Next Application.CommandBars(1).Controls("Файл").Controls( _ CONTROLNAME).Delete MsgBox "Команда ""Закрыть и удалить"" успешно удалена _ из меню ""Файл""" End Sub Sub CloseAndKill() Dim tmpAnswer As Variant If ActiveWorkbook Is Nothing Then Exit Sub tmpAnswer = MsgBox("Вы действительно хотите удалить _ рабочую книгу " & ActiveWorkbook.FullName & """?", _ vbYesNoCancel + vbInformation) If tmpAnswer = vbYes Then Dim tmpFileName As String Dim RecentFle As RecentFile tmpFileName = ActiveWorkbook.FullName ' Удалить имя файла активной рабочей книги из списка ' ранее открывавшихся файлов. For Each RecentFle In Application.RecentFiles If RecentFle.Path = tmpFileName Then RecentFle.Delete Next ActiveWorkbook.Close SaveChanges:=False On Error Resume Next Kill tmpFileName ' Удалить файл. If Err.Number <> 0 Then MsgBox "Невозможно удалить файл """ & tmpFileName & """." End If End If End Sub 370 Часть II Автоматизация Excel Импорт CSVLфайлов Макрос OpenLargeCSVFast любезно предоставлен Масару Каджи (Masaru Kaji), проживающим в КобеYСити, Япония. Масару автор WebYсайта Colo’s Excel Junk Room (http://www.puremis.net/excel/), посвященного Excel и VBA. Следующий макрос импортирует CSVYфайл в Excel, а затем удаляет его. Option Base 1 Sub OpenLargeCSVFast() Dim buf(1 To 256) As Variant Dim i As Long Const strFilePath As String = "C:\temp\Test.CSV" 'Подставьте _ сюда путь к нужному файлу. Dim strRenamedPath As String strRenamedPath = Split(strFilePath, ".")(0) & "txt" With Application .ScreenUpdating = False .DisplayAlerts = False End With 'Создание массива для параметра FieldInfo метода OpenText. For i = 1 To 256 buf(i) = Array(i, 2) Next Name strFilePath As strRenamedPath Workbooks.OpenText Filename:=strRenamedPath, _ DataType:=xlDelimited, Comma:=True, FieldInfo:=buf Erase buf ActiveSheet.UsedRange.Copy ThisWorkbook.Sheets( _ "CSV").Range("A5") ActiveWorkbook.Close False Kill strRenamedPath With Application .ScreenUpdating = True .DisplayAlerts = True End With End Sub Считывание текстового файла в память и его последующий анализ Макрос ReadTxtLines любезно предоставлен Суатом Мехметом Озгуром (Suat Mehmet Ozgur), проживающим в Стамбуле, Турция. Суат разрабатывает ExcelY, AccessY и Visual BasicYприложения для компаний MrExcel Consulting и TheOfficeExperts.com. Следующий макрос реализует весьма необычный подход к считыванию соY держимого текстового файла. Вместо построчного чтения, макрос загружает весь файл в память и сохраняет его в строковой переменной. Затем полученY ная переменная разбивается на строки. Преимущество этого метода заключаY Excel всемогущий Глава 13 ется в единственном обращении к файлу на диске. Последующая обработка файла осуществляется в памяти компьютера. Sub ReadTxtLines() 'Нет необходимости устанавливать библиотеку 'Microsoft Scripting Runtime, поскольку 'в этом макросе используется позднее связывание. Dim sht As Worksheet Dim fso As Object Dim fil As Object Dim txt As Object Dim strtxt As String Dim tmpLoc As Long 'Работаем с активным рабочим листом. Set sht = ActiveSheet 'Очистить содержимое активного рабочего листа. sht.UsedRange.ClearContents 'Создать объект файловой системы. Set fso = CreateObject("Scripting.FileSystemObject") 'Создать объект требуемого файла. Set fil = fso.GetFile("c:\test.csv") 'Открыть файл как текст. Set txt = fil.OpenAsTextStream(1) 'Считать содержимое файла в строковую переменную. strtxt = txt.ReadAll 'Закрыть файл. txt.Close 'Найти позицию 1-го символа новой строки. tmpLoc = InStr(1, strtxt, vbCrLf) 'Выполнять до тех пор, пока в переменной strtxt 'будут оставаться символы новой строки. Do Until tmpLoc = 0 'Сохранить строку в следующей пустой ячейке столбца А. sht.Cells(65536, 1).End(xlUp).Offset(1).Value = _ Left(strtxt, tmpLoc - 1) ' Удалить "отработанную" строку из переменной. strtxt = Right(strtxt, Len(strtxt) - tmpLoc - 1) 'Найти позицию следующего символа новой строки. tmpLoc = InStr(1, strtxt, vbCrLf) Loop 'Последняя строка не содержит символа новой строки. sht.Cells(65536, 1).End(xlUp).Offset(1).Value = strtxt 'Несмотря на то что файл уже закрыт, правила "хорошего тона" 'требуют установить значение переменной fso равным Nothing. Set fso = Nothing End Sub 371 372 Часть II Автоматизация Excel Объединение и разделение рабочих книг Следующие 4 макроса демонстрируют возможность объединения нескольY ких рабочих книг в одну, а также сохранения листов рабочей книги в виде отY дельных рабочих книг или документов Word. Сохранение листов рабочей книги в виде отдельных рабочих книг Макрос SplitWorkbook любезно предоставлен Томми Майлзом (Tommy Miles), проживающим в Хьюстоне, штат Техас, США. Следующий макрос сохраняет все листы активной рабочей книги в виде отдельных рабочих книг, файлы которых носят имена соответствующих рабоY чих листов и размещаются в той же папке, что и исходная рабочая книга. При попытке перезаписать существующий файл выводится предупреждение. Sub SplitWorkbook() Dim ws As Worksheet Dim DisplayStatusBar As Boolean DisplayStatusBar = Application.DisplayStatusBar Application.DisplayStatusBar = True Application.ScreenUpdating = False For Each ws In ThisWorkbook.Sheets Dim NewFileName As String Application.StatusBar = "Осталось рабочих листов: " & _ ThisWorkbook.Sheets.Count If ThisWorkbook.Sheets.Count <> 1 Then NewFileName = ThisWorkbook.Path & "\" & ws.Name & _ ".xls" ws.Copy ActiveWorkbook.Sheets(1).Name = "Sheet1" ActiveWorkbook.SaveAs Filename:=NewFileName ActiveWorkbook.Close SaveChanges:=False Else NewFileName = ThisWorkbook.Path & "\" & ws.Name & _ ".xls" ws.Name = "Sheet1" ThisWorkbook.SaveAs Filename:=NewFileName End If Next Application.StatusBar = False Application.DisplayStatusBar = DisplayStatusBar Application.ScreenUpdating = True End Sub Объединение нескольких рабочих книг в одну Макрос CombineWorkbooks также любезно предоставлен Томми Майлзом. Следующий макрос объединяет все рабочие книги, расположенные в заY данной в папке, в одну. Рабочие листы полученной книги будут названы по именам соответствующих исходных рабочих книг. Excel всемогущий Глава 13 373 Sub CombineWorkbooks() Dim CurFile As String Dim DestWB As Workbook Dim ws As Object 'Рабочие листы могут быть произвольного типа. Const DirLoc As String = "C:\Data\" 'Местоположение _ исходных файлов. Application.ScreenUpdating = False Set DestWB = Workbooks.Add(xlWorksheet) CurFile = Dir(DirLoc & "*.xls") Do While CurFile <> vbNullString Dim OrigWB As Workbook Set OrigWB = Workbooks.Open(Filename:=DirLoc & CurFile, _ ReadOnly:=True) CurFile = Left(Left(CurFile, Len(CurFile) - 4), 29) 'Получение базового имени 'рабочего листа путем отсечения 'последних 4-х символов имени 'исходного файла (".xls"). For Each ws In OrigWB.Sheets ws.Copy After:=DestWB.Sheets(DestWB.Sheets.Count) If OrigWB.Sheets.Count > 1 Then DestWB.Sheets(DestWB.Sheets.Count).Name = _ CurFile & ws.Index Else DestWB.Sheets(DestWB.Sheets.Count).Name = CurFile End If Next OrigWB.Close SaveChanges:=False CurFile = Dir Loop Application.DisplayAlerts = False DestWB.Sheets(1).Delete Application.DisplayAlerts = True Application.ScreenUpdating = True Set DestWB = Nothing End Sub Фильтрация данных с последующим копированием полученного результата в отдельные рабочие листы Макрос Filter_NewSheet любезно предоставлен Деннисом ВалентайY ном (Dennis Wallentin), проживающим в Остерсунде, Швеция. Деннис дает советы, касающиеся использования Excel и VBA, на своем собственном WebY сайте по адресу: www.xldennis.com. 374 Часть II Автоматизация Excel Следующий макрос фильтрует исходные данные (рис. 13.2) и копирует поY лученные результаты в отдельные рабочие листы (рис. 13.3). Рис. 13.2. Исходные данные Sub Dim Dim Dim Dim Рис. 13.3. Результат применения фильтра скопирован в новый ра& бочий лист Filter_NewSheet() wbBook As Workbook wsSheet As Worksheet rnStart As Range, rnData As Range i As Long Set wbBook = ThisWorkbook Set wsSheet = wbBook.Worksheets("Фильтр и копирование") With wsSheet 'Убедитесь, что 1-я строка содержит заголовки столбцов. Set rnStart = .Range("A2") Set rnData = .Range(.Range("A2"), .Range("C65536").End(xlUp)) End With Application.ScreenUpdating = True For i = 1 To 5 'Применение расширенного фильтра. rnStart.AutoFilter Field:=1, Criteria1:="AA" & i 'Копирование результата фильтрации. rnData.SpecialCells(xlCellTypeVisible).Copy 'Добавление нового рабочего листа. Worksheets.Add Before:=wsSheet 'Присвоение имени новому рабочему листу. ActiveSheet.Name = "AA" & i 'Вставка результата фильтрации 'в новый рабочий лист. Range("A2").PasteSpecial xlPasteValues Next i 'Вернуться к исходным данным. rnStart.AutoFilter Field:=1 Excel всемогущий Глава 13 375 With Application 'Очистить буфер обмена. .CutCopyMode = False .ScreenUpdating = False End With End Sub Экспорт данных в Word Макрос Export_Data_Word_Table также любезно предоставлен ДенниY сом Валентайном. Следующий макрос экспортирует данные с рабочего листа Excel в докуY мент Word. Поскольку используется раннее связывание, необходимо добавить ссылку (команда меню редактора Visual Basic Tools References (Сервис Ссылки)) на библиотеку Microsoft Word Object Library. Sub Dim Dim Dim Dim Dim Dim Dim Dim Export_Data_Word_Table() wdApp As Word.Application wdDoc As Word.Document wdCell As Word.Cell i As Long wbBook As Workbook wsSheet As Worksheet rnData As Range vaData As Variant Set wbBook = ThisWorkbook Set wsSheet = wbBook.Worksheets("Экспорт в Word") With wsSheet Set rnData = .Range("A1:A10") End With 'Поместить данные из диапазона A1:A10 в одномерный массив Variant. vaData = rnData.Value 'Создать объект Word. Set wdApp = New Word.Application 'Документ Test.doc должен находиться в той же папке, 'что и рабочая книга. Set wdDoc = wdApp.Documents.Open(ThisWorkbook.Path & "\Test.doc") 'Импортировать данные в 1-й столбец 1-й таблицы. For Each wdCell In wdDoc.Tables(1).Columns(1).Cells i = i + 1 wdCell.Range.Text = vaData(i, 1) Next wdCell 'Сохранить и закрыть документ. With wdDoc .Save .Close End With 376 Часть II Автоматизация Excel 'Завершить работу скрытой копии Microsoft Word. wdApp.Quit 'Удалить внешние переменные из памяти. Set wdDoc = Nothing Set wdApp = Nothing MsgBox "Данные были успешно экспортированы в документ Test.doc.", _ vbInformation End Sub Работа с примечаниями В большинстве случаев примечания ячеек Excel используются не достаточY но эффективно. Рассматриваемые в следующих разделах макросы помогут исY править это упущение. Вывод примечаний Макрос ListComments любезно предоставлен Томми Майлзом. Excel позволяет печатать примечания, однако не позволяет выводить инY формацию о рабочей книге и (или) рабочем листе, к которым относится то или иное примечание. Следующий макрос помещает примечания, имена их авторов и сведения о местоположении в новую рабочую книгу для последуюY щего просмотра, сохранения и (или) печати. Sub ListComments() Dim wb As Workbook Dim ws As Worksheet Dim cmt As Comment Dim cmtCount As Long cmtCount = 2 On Error Resume Next Set ws = ActiveSheet If ws Is Nothing Then Exit Sub On Error GoTo 0 Application.ScreenUpdating = False Set wb = Workbooks.Add(xlWorksheet) With wb.Sheets(1) .Range("$A$1") .Range("$B$1") .Range("$C$1") .Range("$D$1") .Range("$E$1") End With = = = = = "Автор" "Рабочая книга" "Рабочий лист" "Диапазон" "Примечание" For Each cmt In ws.Comments Excel всемогущий With wb.Sheets(1) .Cells(cmtCount, .Cells(cmtCount, .Cells(cmtCount, .Cells(cmtCount, .Cells(cmtCount, cmt.Text) End With 1) 2) 3) 4) 5) = = = = = Глава 13 377 cmt.author cmt.Parent.Parent.Parent.Name cmt.Parent.Parent.Name cmt.Parent.Address CleanComment(cmt.author, _ cmtCount = cmtCount + 1 Next wb.Sheets(1).UsedRange.WrapText = False Application.ScreenUpdating = True Set ws = Nothing Set wb = Nothing End Sub Private Function CleanComment(author As String, _ cmt As String) As String Dim tmp As String tmp = Application.WorksheetFunction.Substitute(cmt, _ author & ":", "") tmp = Application.WorksheetFunction.Substitute(tmp, _ Chr(10), "") CleanComment = tmp End Function Результат выполнения макроса ListComments показан на рис. 13.4. Рис. 13.4. Макрос ListComments позволяет получить исчерпывающую информацию о примечаниях Изменение размера области примечания Макрос CommentFitter1 любезно предоставлен Томом Уртисом. Следующий макрос изменяет размер области примечания так, чтобы она вместила в себя весь текст примечания. Sub CommentFitter1() Application.ScreenUpdating = False Dim x As Range, y As Long For Each x In Cells.SpecialCells(xlCellTypeComments) Select Case True Case Len(x.NoteText) <> 0 With x.Comment 378 Часть II Автоматизация Excel .Shape.TextFrame.AutoSize = True If .Shape.Width > 250 Then y = .Shape.Width * .Shape.Height .Shape.Width = 150 .Shape.Height = (y / 200) * 1.2 End If End With End Select Next x Application.ScreenUpdating = True End Sub Результат выполнения макроса CommentFitter1 показан на рис. 13.5. Рис. 13.5. Теперь область примечания включает в себя весь его текст Изменение размера области примечания с помощью центрирования Макрос CommentFitter2 также любезно предоставлен Томом Уртисом. Следующий макрос изменяет размер области примечания путем центрироY вания текста примечания. Sub CommentFitter2() Application.ScreenUpdating = False Dim x As Range, y As Long For Each x In Cells.SpecialCells(xlCellTypeComments) Select Case True Case Len(x.NoteText) <> 0 With x.Comment .Shape.TextFrame.AutoSize = True If .Shape.Width > 250 Then y = .Shape.Width * .Shape.Height .Shape.ScaleHeight 0.9, msoFalse, _ msoScaleFromTopLeft .Shape.ScaleWidth 1#, msoFalse, _ msoScaleFromTopLeft End If End With End Select Next x Application.ScreenUpdating = True End Sub Результат выполнения макроса CommentFitter2 показан на рис. 13.6. Excel всемогущий Глава 13 379 Рис. 13.6. Результат центрирования текста примечания Размещение диаграммы в примечании Макрос PlaceGraph любезно предоставлен Томом Уртисом. Несмотря на то что Excel не позволяет разместить в примечании ‘‘настоящую’’ диаграмму, это можно сделать с ее изображением, как показано на рис. 13.7. Рис. 13.7. “Диаграмма”, размещенная в примечании Чтобы поместить изображение диаграммы в примечание с помощью польY зовательского интерфейса Excel, выполните следующие действия. 1. Создайте требуемое изображение. 2. Создайте примечание и выделите соответствующую ячейку. 380 Часть II Автоматизация Excel 3. Выберите команду меню Excel Вставка Изменить примечание (Insert Edit Comment) или щелкните на ячейке правой кнопкой мыши и выбериY те команду контекстного меню Изменить примечание (Edit Comment). 4. Щелкните правой кнопкой мыши на границе области примечания и выберите команду контекстного меню Формат примечания (Format Comment). 5. Перейдите во вкладку Цвета и линии (Colors and Lines) и раскройте список Цвет (Color), расположенный в области Заливка (Fill). 6. Выберите команду Способы заливки (Fill Effects), перейдите во вкладку Рисунок (Picture) открывшегося диалогового окна Способы заливки (Fill Effects) и щелкните на кнопке Рисунок (Picture). 7. Выберите требуемое изображение и щелкните на кнопке OK для закрыY тия диалогового окна Способы заливки. Еще раз щелкните на кнопке OK, для того чтобы закрыть диалоговое окно Формат примечания. Чтобы ‘‘диаграмма’’, помещенная в примечание описанным выше образом, всегда содержала текущие сведения, включите следующий код VBA в обработY чик события SheetChange, срабатывающего при изменении исходных данных диаграммы (укажите требуемое имя файла изображения, название диаграммы, название рабочего листа, адрес ячейки и размер области примечания). Sub PlaceGraph() Dim x As String, z As Range Application.ScreenUpdating = False x = "C:\XWMJGraph.gif" Set z = Worksheets("Диаграмма в примечании").Range("A3") On Error Resume Next z.Comment.Delete On Error GoTo 0 ActiveSheet.ChartObjects("Chart 1").Activate ActiveChart.Export x With z.AddComment With .Shape .Height = 322 .Width = 465 .Fill.UserPicture x End With End With Range("A1").Activate Application.ScreenUpdating = True Set z = Nothing End Sub Excel всемогущий Глава 13 381 Замечательные возможности Excel VBA В следующих разделах рассматриваются макросы, демонстрирующие богаY тые возможности Excel VBA. Выделение ячейки с помощью условного форматирования Макрос Worksheet_SelectionChange любезно предоставлен Иваном Ф. Моалой (Ivan F. Moala), проживающим в Окленде, Новая Зеландия. Иван YYYY автор WebYсайта The XcelFiles (www.xcelfiles.com), посвященного задачам, решить которые с помощью Excel представляется невероятным или невозможным. Следующий макрос выделяет активную ячейку с помощью условного форY матирования. Следует отметить, что макрос Worksheet_SelectionChange удаляет любое существующее условное форматирование рабочего листа, а также очищает буфер обмена, что приводит к затруднению выполнения опеY раций вырезания, копирования и вставки данных. '///////////////////////////////////////////////////// '// Исправлено 14 февраля 2003 - с комментариями '// Хуана Пабло Г. (Juan Pablo G.). '// Локализованные версии Excel могут неправильно '// интерпретировать значение TRUE. '// Воспользуемся фактом, что TRUE можно '// заменить любым целым числом, не равным 0. '//////////////////////////////////////////////////// Const iInternational As Integer = Not (0) Private Sub Worksheet_SelectionChange(ByVal Target As Range) Dim iColor As Integer '// Примечание: этот макрос удаляет любое '// существующее условное форматирование! '// Продолжить выполнение макроса, если '// пользователь выделит диапазон ячеек. On Error Resume Next iColor = Target.Interior.ColorIndex '// Оставить действие строки On Error Resume Next '// в силе на случай возникновения ошибок смещения строк. If iColor < 0 Then iColor = 36 Else iColor = iColor + 1 End If '// Проверить, совпадает ли цвет текста с цветом заливки. If iColor = Target.Font.ColorIndex Then iColor = iColor + 1 Cells.FormatConditions.Delete '// Выделение с помощью горизонтальной полосы. With Range("A" & Target.Row, Target.Address) 'Rows(Target.Row) .FormatConditions.Add Type:=2, Formula1:=iInternational ' "TRUE" .FormatConditions(1).Interior.ColorIndex = iColor 382 Часть II Автоматизация Excel End With '// Выделение с помощью вертикальной полосы. With Range(Target.Offset(1 - Target.Row, 0).Address & ":" & _ Target.Offset(-1, 0).Address) .FormatConditions.Add Type:=2, Formula1:=iInternational ' "TRUE" .FormatConditions(1).Interior.ColorIndex = iColor End With End Sub Выделение ячейки без применения условного форматирования Макрос HighLight и вспомогательные макросы HighlightRight, HighlightLeft, HighlightUp, HighlightDown, DisableDelete и ReSet также любезно предоставлены Иваном Ф. Моалой. Следующий макрос выделяет ячейку, которая была сделана активной в реY зультате использования клавиш управления курсором, без применения условY ного форматирования. '// '// '// '// '// '// '// '// '// '// '// '// '// Альтернативный вариант макроса, выделяющего активную ячейку: - не использует условное форматирование; - сохраняет существующее условное форматирование рабочего листа; - позволяет выполнять операции вырезания, копирования и вставки данных. Макрос создан Нейтом Оливером (Nate Oliver) для Альдо (Aldo). Альдо предложил обработку нажатия клавиши <Del>. Спасибо, друзья!! См. дополнительные комментарии в коде. '// Размещено в стандартном модуле. Dim strCol As String Dim iCol As Integer Dim dblRow As Double Sub HighlightRight() HighLight 0, 1 End Sub Sub HighlightLeft() HighLight 0, -1 End Sub Sub HighlightUp() HighLight -1, 0, -1 End Sub Sub HighlightDown() HighLight 1, 0, 1 End Sub Sub HighLight(dblxRow As Double, iyCol As Integer, _ Excel всемогущий Optional dblZ As Double = 0) On Error GoTo NoGo strCol = Mid(ActiveCell.Offset(dblxRow, iyCol).Address, _ InStr(ActiveCell.Offset(dblxRow, iyCol).Address, "$") + 1, _ InStr(2, ActiveCell.Offset(dblxRow, iyCol).Address, "$") - 2) iCol = ActiveCell.Column dblRow = ActiveCell.Row '// Позволяет избежать мерцания экрана. Application.ScreenUpdating = False With Range(strCol & ":" & strCol & "," & dblRow + dblZ & _ ":" & dblRow + dblZ) .Select '// Сейчас экран следует обновить. Application.ScreenUpdating = True .Item(dblRow + dblxRow).Activate End With NoGo: End Sub Sub DisableDelete() Cells(ActiveCell.Row, ActiveCell.Column).Select Application.OnKey "{DEL}" End Sub Sub ReSet() Application.OnKey Application.OnKey Application.OnKey Application.OnKey End Sub "{RIGHT}" "{LEFT}" "{UP}" "{DOWN}" Транспонирование данных Макрос TransposeData любезно предоставлен Масару Каджи. Следующий макрос транспонирует данные по заданному столбцу. Sub TransposeData() Dim shOrg As Worksheet, shRes As Worksheet Dim rngStart As Range, rngPaste As Range Dim lngData As Long Application.ScreenUpdating = False On Error Resume Next Application.DisplayAlerts = False Sheets("Транспонирование (результат)").Delete Application.DisplayAlerts = True On Error GoTo 0 On Error GoTo terminate Set shOrg = Sheets("Транспонирование") Set shRes = Sheets.Add(After:=shOrg) shRes.Name = "Транспонирование (результат)" With shOrg 'Отсортировать. .Cells.CurrentRegion.Sort Key1:=.[B2], Order1:=1, _ Глава 13 383 384 Часть II Автоматизация Excel Key2:=.[C2], Order2:=1, Key3:=.[E2], Order3:=1, Header:=xlYes 'Скопировать заголовок. .Rows(1).Copy shRes.Rows(1) 'Задать начальный диапазон. Set rngStart = .[C2] Do Until IsEmpty(rngStart) Set rngPaste = shRes.[A65536].End(xlUp).Offset(1) lngData = GetNextRange(rngStart) rngStart.Offset(, -2).Resize(, 5).Copy rngPaste 'Копировать V1 в V14. rngStart.Offset(, 2).Resize(lngData).Copy rngPaste.Offset(, 5).PasteSpecial Paste:=xlAll, _ Operation:=xlNone, SkipBlanks:=False, Transpose:=True 'Копировать V1FP в V14FP. rngStart.Offset(, 1).Resize(lngData).Copy rngPaste.Offset(, 19).PasteSpecial Paste:=xlAll, _ Operation:=xlNone, SkipBlanks:=False, Transpose:=True Set rngStart = rngStart.Offset(lngData) Loop End With Application.GoTo shRes.[A1] shRes.Cells.Columns.AutoFit Application.ScreenUpdating = True Application.CutCopyMode = False If MsgBox("Удалить исходный рабочий лист?", 36) = 6 Then Application.DisplayAlerts = False Sheets("Транспонирование").Delete Application.DisplayAlerts = True End If Set rngPaste = Nothing Set rngStart = Nothing Set shRes = Nothing Exit Sub terminate: End Sub Function GetNextRange(ByVal rngSt As Range) As Long Dim i As Long i = 0 Do Until rngSt.Value <> rngSt.Offset(i).Value i = i + 1 Loop GetNextRange = i End Function Выделение и отмена выделения несмежных ячеек Макросы ModifyRightClick, DeselectActiveCell и DeselectActiveArea любезно предоставлены Томом Уртисом. Чтобы отменить выделение ячейки или диапазона ячеек на рабочем листе, нужно щелкнуть на произвольной невыделенной ячейке. После выполнения этой операции выделение требуемых ячеек необходимо начинать заново, что весьма проблематично, если речь идет о большом количестве несмежных ячеек. Excel всемогущий Глава 13 385 Следующий макрос добавляет в контекстное меню 2 новые команды: Отменить выделение активной ячейки и Отменить выделение активной области (рис. 13.8). Рис. 13.8. Процедура ModifyRightClick создает 2 новые команды контекстного меню Удерживая нажатой клавишу <Ctrl>, щелкните левой кнопкой мыши на ячейке несмежного диапазона, выделение которой следует отменить. ЩелкY ните правой кнопкой мыши и выберите команду контекстного меню Отменить выделение активной ячейки (для отмены выделения только одной активной ячейки) или Отменить выделение активной области (для отмены выделения области, которой принадлежит активная ячейка). Поместите приведенный ниже код в стандартный модуль. Sub ModifyRightClick() Dim O1 As Object, O2 As Object On Error Resume Next With CommandBars("Cell") .Controls("Отменить выделение активной ячейки").Delete .Controls("Отменить выделение активной области").Delete End With On Error GoTo 0 Set O1 = CommandBars("Cell").Controls.Add With O1 .Caption = "Отменить выделение активной ячейки" .OnAction = "DeselectActiveCell" End With Set O2 = CommandBars("Cell").Controls.Add With O2 .Caption = "Отменить выделение активной области" .OnAction = "DeselectActiveArea" End With End Sub 386 Часть II Автоматизация Excel Sub DeselectActiveCell() Dim x As Range, y As Range If Selection.Cells.Count > 1 Then For Each y In Selection.Cells If y.Address <> ActiveCell.Address Then If x Is Nothing Then Set x = y Else Set x = Application.Union(x, y) End If End If Next y If x.Cells.Count > 0 Then x.Select End If End If End Sub Sub DeselectActiveArea() Dim x As Range, y As Range If Selection.Areas.Count > 1 Then For Each y In Selection.Areas If Application.Intersect(ActiveCell, y) Is Nothing Then If x Is Nothing Then Set x = y Else Set x = Application.Union(x, y) End If End If Next y x.Select End If End Sub Разместите следующий код в модуле ЭтаКнига (ThisWorkbook). Private Sub Workbook_Deactivate() Application.CommandBars("Cell").Reset End Sub Private Sub Workbook_Activate() ModifyRightClick End Sub VBA для профессионалов VBAYпрограммисты находятся в постоянном поиске более эффективных решений тех или иных задач. Размещая результаты своей работы в Internet, они оказывают неоценимую услугу всему сообществу VBAYпрограммистов. Установка параметров страницы Макросы Macro1, Macro1_Version2, Macro1_Version3 и Macro1_ Version4 любезно предоставлены Хуаном Пабло Гонсалесом (Juan Pablo Excel всемогущий Глава 13 387 Gonzales), проживающим в Боготе, Колумбия. Хуан Пабло YYYY разработчик программы F&I Menu Wizard, он также выполняет для Mr.Excel.com все закаY зы, поступающие от испаноязычных клиентов. Следующие макросы выполняют одно и то же действие: устанавливают разY меры верхнего, нижнего, левого и правого полей страницы равными 1,5 дюйма (3,8 см), а размеры полей верхнего колонтитула и нижнего колонтитула YYYY равY ными 1 дюйму (2,5 см). Макрос Macro1 создан средством записи макросов. Макросы Macro1_Version2, Macro1_Version3 и Macro1_Version4 деY монстрируют возможность улучшения автоматически сгенерированного кода с целью повышения его производительности. На рис. 13.9 показана сравниY тельная таблица скорости выполнения всех четырех макросов. Рис. 13.9. Сравнительная таблица скорости выполнения макросов, устанавливающих параметры страницы Sub Macro1() ' ' Macro1 Macro ' Macro recorded 12/2/2003 by Juan Pablo Gonzalez ' With ActiveSheet.PageSetup .PrintTitleRows = "" .PrintTitleColumns = "" End With ActiveSheet.PageSetup.PrintArea = "" With ActiveSheet.PageSetup .LeftHeader = "" .CenterHeader = "" .RightHeader = "" .LeftFooter = "" .CenterFooter = "" .RightFooter = "" 388 Часть II Автоматизация Excel .LeftMargin = Application.InchesToPoints(1.5) .RightMargin = Application.InchesToPoints(1.5) .TopMargin = Application.InchesToPoints(1.5) .BottomMargin = Application.InchesToPoints(1.5) .HeaderMargin = Application.InchesToPoints(1) .FooterMargin = Application.InchesToPoints(1) .PrintHeadings = False .PrintGridlines = False .PrintComments = xlPrintNoComments .CenterHorizontally = False .CenterVertically = False .Orientation = xlPortrait .Draft = False .PaperSize = xlPaperLetter .FirstPageNumber = xlAutomatic .Order = xlDownThenOver .BlackAndWhite = False .Zoom = 100 End With End Sub Как обычно, средство записи макросов создало весьма громоздкий код. Учитывая низкую скорость обновления объекта PageSetup, макрос Macro1 остро нуждается в оптимизации, как показано ниже: Sub Macro1_Version2() With ActiveSheet.PageSetup .LeftMargin = Application.InchesToPoints(1.5) .RightMargin = Application.InchesToPoints(1.5) .TopMargin = Application.InchesToPoints(1.5) .BottomMargin = Application.InchesToPoints(1.5) .HeaderMargin = Application.InchesToPoints(1) .FooterMargin = Application.InchesToPoints(1) End With End Sub Среднее время выполнения макроса Macro1_Version2 более чем на 70% меньше среднего времени выполнения макроса Macro1, однако это еще далеко не все. Принимая во внимание низкую скорость обновления объекта PageSetup, воспользуемся выражением If для установки значения только тех паY раметров страницы, которые этого требуют. В приведенном ниже макросе вызов функции Application.InchesToPoints был заменен фактическими значениями параметров страницы. Sub Macro1_Version3() With ActiveSheet.PageSetup If .LeftMargin <> 108 Then .LeftMargin = 108 If .RightMargin <> 108 Then .RightMargin = 108 If .TopMargin <> 108 Then .TopMargin = 108 If .BottomMargin <> 108 Then .BottomMargin = 108 If .HeaderMargin <> 72 Then .HeaderMargin = 72 If .FooterMargin <> 72 Then .FooterMargin = 72 End With End Sub Excel всемогущий Глава 13 389 Разница во времени выполнения макросов Macro1_Version2 и Macro1_ Version3 становится заметной при условии, что некоторые параметры страY ницы уже содержат нужные значения. Макрос Macro1_Version4 вызывает XLMYметод PAGE.SETUP, позволяя тем самым сократить среднее время своего выполнения более чем на 95% по сравнению со средним временем выполнения макроса Macro1. Параметры метода PAGE.SETUP left, right, top, bot, head_margin и foot_margin измеряются не в точках, а в дюймах. Sub Macro1_Version4() Dim St As String St = "PAGE.SETUP(, , " & _ "1.5, 1.5, 1.5, 1.5" & _ ", 0, False, False, False, 1, 1, True, 1, 1,False, , " & _ "1, 1" & _ ", False)" Application.ExecuteExcel4Macro St End Sub Параметрам left, right, top и bot соответствует 4Yя строка приведенY ного выше кода, параметрам head_margin и foot_margin YYYY 6Yя строка. К сожалению, макрос Macro1_Version4 имеет два существенных недостатY ка. ВоYпервых, он основан на языке XLM, включенном в Excel для обеспечеY ния обратной совместимости. Неизвестно, как долго Microsoft намерена подY держивать этот язык. ВоYвторых, ошибка при указании параметров метода PAGE.SETUP приведет к тому, что этот метод не будет выполнен без какогоY либо уведомления. Вычисление времени выполнения кода макроса Ниже приведен код, использующийся для вычисления времени выполнения макросов Macro1, Macro1_Version2, Macro1_Version3 и Macro1_ Version4 (см. рис. 13.9). Public Declare Function QueryPerformanceFrequency Lib _ "kernel32" (lpFrequency As Currency) As Long Public Declare Function QueryPerformanceCounter Lib _ "kernel32.dll" (lpPerformanceCount As Currency) As Long Sub Test() Dim Ar(1 To 20, 1 To 4) As Currency, WS As Worksheet Dim n As Currency, str As Currency, fin As Currency Dim y As Currency Dim i As Long, j As Long Application.ScreenUpdating = False For i = 1 To 4 For j = 1 To 20 Set WS = ThisWorkbook.Sheets.Add WS.Range("A1").Value = 1 390 Часть II Автоматизация Excel QueryPerformanceFrequency y QueryPerformanceCounter str Select Case i Case 1: Macro1 Case 2: Macro1_Version2 Case 3: Macro1_Version3 Case 4: Macro1_Version4 End Select QueryPerformanceCounter fin Application.DisplayAlerts = False WS.Delete Application.DisplayAlerts = True n = (fin - str) Ar(j, i) = CCur(Format(n, _ "##########.############") / y) Next j Next i With Range("A8").Resize(1, 4) .Value = Array("Macro1", "Macro1_Version2", _ "Macro1_Version3", "Macro1_Version4") .Font.Bold = True End With Range("A9").Resize(20, 4).Value = Ar With Range("A29").Resize(1, 4) ' В англоязычной версии Excel: ' .FormulaR1C1 = "=AVERAGE(R2C:R21C)" .FormulaR1C1Local = "=СРЗНАЧ(R2C:R21C)" ' В англоязычной версии Excel: ' .Offset(1).FormulaR1C1 = "=RANK(R22C, R22C1:R22C4, 1)" .Offset(1).FormulaR1C1Local = "=РАНГ(R22C; R22C1:R22C4; 1)" .Resize(2).Font.Bold = True End With Application.ScreenUpdating = True End Sub Запрет/разрешение выполнения операций вырезания, копирования и вставки Макросы EnableAllClear и DisAbleAllCLear любезно предоставлены Иваном Ф. Моалой. Иногда изменение пользователями содержимого рабочего листа крайне нежелательно. Следующий код запрещает/разрешает все способы выполнения операций вырезания, копирования и вставки данных. Option Private Module Dim ComBar As CommandBar Dim ComBarCtrl As CommandBarControl Sub EnableAllClear() '// Команда Вставка (Insert).. EnableControl 295, True '// ..Ячейки (Cells) EnableControl 296, True '// ..Строки (Rows) EnableControl 297, True '// ..Столбцы (Columns) Excel всемогущий '// Команда... EnableControl 478, True EnableControl 292, True EnableControl 293, True EnableControl 294, True EnableControl 847, True '// Команда... EnableControl 3125, True EnableControl 1964, True EnableControl 872, True EnableControl 873, True EnableControl 874, True '// Команда... EnableControl EnableControl EnableControl EnableControl 21, True 19, True 22, True 755, True '// '// '// '// '// '// '// '// '// Глава 13 391 Правка->Удалить (Edit->Delete) Удалить (Delete), контекстное меню ячейки Удалить строки (Delete Rows), контекстное меню строки Удалить (Delete), контекстное меню столбца Правка->Удалить лист (Edit->Delete Sheet) '// '// '// '// '// '// '// '// '// Очистить содержимое (Clear Contents), контекстное меню Правка->Очистить->Все (All) Правка->Очистить->Форматы (Edit->Clear->Formats) Правка->Очистить->Содержимое (Edit->Clear->Contents) Правка->Очистить->Примечания (Edit->Clear->Comments) '// '// '// '// '// Вырезать (Cut) Копировать (Copy) Вставить (Paste) Специальная вставка (Paste Special) '// Комбинации клавиш. With Application .OnKey "^c" .OnKey "^v" .OnKey "+{DEL}" .OnKey "+{INSERT}" .CellDragAndDrop = True .OnDoubleClick = "" End With '// Панели инструментов. CommandBars("ToolBar List").Enabled = True End Sub Sub DisAbleAllCLear() '// Команда Вставка (Insert).. EnableControl 295, False '// EnableControl 296, False '// EnableControl 297, False '// '// Команда... EnableControl 478, False '// EnableControl 292, False '// '// EnableControl 293, False '// '// EnableControl 294, False '// '// EnableControl 847, False '// '// '// Команда... EnableControl 21, False '// ..Ячейки (Cells) ..Строки (Rows) ..Столбцы (Columns) Правка->Удалить (Edit->Delete) Удалить (Delete), контекстное меню ячейки Удалить строки (Delete Rows), контекстное меню строки Удалить (Delete), контекстное меню столбца Правка->Удалить лист (Edit->Delete Sheet) Вырезать (Cut) 392 Часть II Автоматизация Excel EnableControl 19, False EnableControl 22, False EnableControl 755, False '// '// '// '// Копировать (Copy) Вставить (Paste) Специальная вставка (Paste Special) '// Команда... EnableControl 3125, False '// Очистить содержимое (Clear '// Contents), контекстное меню EnableControl 1964, False '// Правка->Очистить->Все (All) EnableControl 872, False '// Правка->Очистить->Форматы '// (Edit->Clear->Formats) EnableControl 873, False '// Правка->Очистить->Содержимое '// (Edit->Clear->Contents) EnableControl 874, False '// Правка->Очистить->Примечания '// (Edit->Clear->Comments) '// Комбинации клавиш. With Application .OnKey "^c", "Dummy" .OnKey "^v", "Dummy" .OnKey "+{DEL}", "Dummy" .OnKey "+{INSERT}", "Dummy" .CellDragAndDrop = False .OnDoubleClick = "Dummy" End With '// Панели инструментов. CommandBars("ToolBar List").Enabled = False End Sub Sub EnableControl(iId As Integer, blnState As Boolean) Dim ComBar As CommandBar Dim ComBarCtrl As CommandBarControl On Error Resume Next For Each ComBar In Application.CommandBars Set ComBarCtrl = ComBar.FindControl(Id:=iId, recursive:=True) If Not ComBarCtrl Is Nothing Then ComBarCtrl.Enabled = blnState Next End Sub Sub Dummy() '// Вывод сообщения. MsgBox "Команда недоступна!" End Sub Определение порядка сортировки Макрос CustomSort любезно предоставлен Вэем Цзянгом (Wei Jiang), проживающим в г. Шиян, Китай. Цзянг работает на должности консультанта в компании MrExcel Consulting. Excel поддерживает сортировку списков в числовом или алфавитном поY рядке. Иногда этого оказывается недостаточно. Как показано на рис. 13.10, желаемый порядок сортировки списка выглядит так: ‘‘Пояса’’, ‘‘Сумки’’, ‘‘Часы’’, ‘‘Бумажники’’, ‘‘Все остальное’’. Excel всемогущий Глава 13 393 Рис. 13.10. Желаемый порядок сортировки списка в ячейках A2:C16 указан в столбце I Следующий макрос сортирует список с учетом заданного порядка сортиY ровки. Sub CustomSort() ' Задать желаемый порядок сортировки. Application.AddCustomList ListArray:=Range("I1:I5") ' Определить номер списка, задающего порядок сортировки. nIndex = Application.GetCustomListNum(Range("I1:I5").Value) ' Отсортировать список, используя заданный порядок сортировки. ' Номер списка, задающего порядок сортировки, равен nIndex + 1, ' поскольку обычный порядок сортировки имеет номер 1. Range("A2:C16").Sort Key1:=Range("B2"), Order1:=xlAscending, _ Header:=xlNo, Orientation:=xlSortColumns, OrderCustom:=nIndex + 1 Range("A2:C16").Sort Key1:=Range("A2"), Order1:=xlAscending, _ Header:=xlNo, Orientation:=xlSortColumns ' Удалить список, задающий порядок сортировки. Application.DeleteCustomList nIndex End Sub Результат выполнения макроса CustomSort показан на рис. 13.11. Рис. 13.11. В результате выполнения макроса список в ячейках A2:C16 отсортирован сперва по дате, а затем — в соответствии с порядком, заданным в столбце I 394 Часть II Автоматизация Excel Создание индикатора хода процесса Макрос Worksheet_Change любезно предоставлен Томом Уртисом. Следующий макрос создает индикатор хода процесса в столбце C, основыY ваясь на данных в столбцах A и B (рис. 13.12). Рис. 13.12. Индикатор хода процесса Private Sub Worksheet_Change(ByVal Target As Range) If Target.Column > 2 Or Target.Cells.Count > 1 Then Exit Sub If Application.IsNumber(Target.Value) = False Then Application.EnableEvents = False Application.Undo Application.EnableEvents = True MsgBox "Введите число." Exit Sub End If Select Case Target.Column Case 1 If Target.Value > Target.Offset(0, 1).Value Then Application.EnableEvents = False Application.Undo Application.EnableEvents = True MsgBox "Значение в столбце A не может быть больше _ значения в столбце B." Exit Sub End If Case 2 If Target.Value < Target.Offset(0, -1).Value Then Application.EnableEvents = False Application.Undo Application.EnableEvents = True MsgBox "Значение в столбце B не может быть меньше _ значения в столбце A." Exit Sub End If End Select Dim x As Long Excel всемогущий Глава 13 395 x = Target.Row Dim z As String z = Range("B" & x).Value - Range("A" & x).Value With Range("C" & x) ' В англоязычной версии Excel: ' .Formula = "=IF(RC[-1]<=RC[-2],REPT(""n"",RC[-1])&REPT(""n"", _ RC[-2]-RC[-1]),REPT(""n"",RC[-2])&REPT(""o"",RC[-1]-RC[-2]))" .FormulaLocal = "=ЕСЛИ(RC[-1]<=RC[-2];ПОВТОР(""n""; _ RC[-1])&ПОВТОР(""n"";RC[-2]-RC[-1]);ПОВТОР(""n""; _ RC[-2])&ПОВТОР(""o"";RC[-1]-RC[-2]))" .Value = .Value .Font.Name = "Wingdings" .Font.ColorIndex = 1 .Font.Size = 10 If Len(Range("A" & x)) <> 0 Then .Characters(1, (.Characters.Count - z)).Font.ColorIndex = 3 .Characters(1, (.Characters.Count - z)).Font.Size = 12 End If End With End Sub Создание защищенного поля для ввода пароля Макрос HiddenPassword любезно предоставлен Даниелем Клэнном (Daniel Klann), проживающим в Сиднее, Австралия. Даниель знаком со мноY жеством различных языков программирования, однако предпочитает разраY ботку приложений на VBA для Excel и Access. Клэнн YYYY автор собственного WebYсайта, который находится по адресу: www.danielklann.com. Использование для ввода пароля обычного текстового поля крайне нежеY лательно, поскольку все вводимые символы отображаются на экране. СлеY дующий макрос заменяет символы пароля знаками звездочки (*), превращая обычное текстовое поле в защищенное поле ввода пароля (рис. 13.13). Рис. 13.13. Макрос HiddenPassword пре& вращает обычное текстовое поле в защищен& ное поля ввода пароля 'Используемые функции API. Private Declare Function CallNextHookEx Lib "user32" _ (ByVal hHook As Long, ByVal ncode As Long, _ ByVal wParam As Long, lParam As Any) As Long Private Declare Function GetModuleHandle Lib "kernel32" Alias _ "GetModuleHandleA" (ByVal lpModuleName As String) As Long 396 Часть II Автоматизация Excel Private Declare Function SetWindowsHookEx Lib "user32" Alias _ "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _ ByVal hmod As Long, ByVal dwThreadId As Long) As Long Private Declare Function UnhookWindowsHookEx Lib "user32" _ (ByVal hHook As Long) As Long Private Declare Function SendDlgItemMessage Lib "user32" Alias _ "SendDlgItemMessageA" (ByVal hDlg As Long, _ ByVal nIDDlgItem As Long, ByVal wMsg As Long, _ ByVal wParam As Long, ByVal lParam As Long) As Long Private Declare Function GetClassName Lib "user32" _ Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName _ As String, ByVal nMaxCount As Long) As Long Private Declare Function GetCurrentThreadId Lib "kernel32" () _ As Long 'Константы, используемые в функциях API. Private Const EM_SETPASSWORDCHAR = &HCC Private Const WH_CBT = 5 Private Const HCBT_ACTIVATE = 5 Private Const HC_ACTION = 0 Private hHook As Long Sub HiddenPassword() If InputBoxDK("Введите пароль", "Ввод пароля") <> _ "password" Then MsgBox "Неверный пароль! Доступ запрещен." Else MsgBox "Верный пароль! Добро пожаловать!" End If End Sub Public Function NewProc(ByVal lngCode As Long, _ ByVal wParam As Long, ByVal lParam As Long) As Long Dim RetVal Dim strClassName As String, lngBuffer As Long If lngCode < HC_ACTION Then NewProc = CallNextHookEx(hHook, lngCode, wParam, lParam) Exit Function End If strClassName = String$(256, " ") lngBuffer = 255 If lngCode = HCBT_ACTIVATE Then 'Окно активировано. Excel всемогущий Глава 13 397 RetVal = GetClassName(wParam, strClassName, lngBuffer) 'Имя класса обычного текстового поля. If Left$(strClassName, RetVal) = "#32770" Then 'Следующая строка превращает обычное текстовое поле в защищенное 'поле ввода пароля. Символ "*" можно заменить на любой другой. SendDlgItemMessage wParam, &H1324, _ EM_SETPASSWORDCHAR, Asc("*"), &H0 End If End If 'Следующая строка гарантирует корректный вызов 'всех обработчиков прерываний. CallNextHookEx hHook, lngCode, wParam, lParam End Function Public Function InputBoxDK(Prompt, Optional Title, _ Optional Default, Optional XPos, Optional YPos, _ Optional HelpFile, Optional Context) As String Dim lngModHwnd As Long, lngThreadID As Long lngThreadID = GetCurrentThreadId lngModHwnd = GetModuleHandle(vbNullString) hHook = SetWindowsHookEx(WH_CBT, AddressOf NewProc, _ lngModHwnd, lngThreadID) On Error Resume Next InputBoxDK = InputBox(Prompt, Title, Default, XPos, YPos, _ HelpFile, Context) UnhookWindowsHookEx hHook End Function Изменение регистра текста Макрос TextCaseChange любезно предоставлен Иваном Ф. Моалой. Word позволяет изменять регистр выделенного текста. Следующий макрос делает возможным изменение регистра текста, находящегося в выделенном диапазоне рабочего листа Excel. Sub Dim Dim Dim Dim Dim TextCaseChange() RgText As Range oCell As Range Ans As String strTest As String sCap As Integer, _ lCap As Integer, _ i As Integer '// Предполагается, что перед вызовом макроса '// пользователь выделил требуемый диапазон текста. Again: 398 Часть II Автоматизация Excel Ans = Application.InputBox("[С]трочные" & vbCr & "[П]рописные" _ & vbCr & "[К]ак в предложениях" & vbCr & "[Н]ачинать с прописных" _ & vbCr & "[М]алые прописные", "Введите букву", Type:=2) If Ans = "False" Then Exit Sub If InStr(1, "СПКНМ", UCase(Ans), vbTextCompare) = 0 Or _ Len(Ans) > 1 Then GoTo Again On Error GoTo NoText If Selection.Count = 1 Then Set RgText = Selection Else Set RgText = Selection.SpecialCells(xlCellTypeConstants, 2) End If On Error GoTo 0 For Each oCell In RgText Select Case UCase(Ans) Case "С": oCell = LCase(oCell.Text) Case "П": oCell = UCase(oCell.Text) Case "К": oCell = UCase(Left(oCell.Text, 1)) & _ LCase(Right(oCell.Text, Len(oCell.Text) - 1)) Case "Н": oCell = Application.WorksheetFunction.Proper( _ oCell.Text) Case "М" lCap = oCell.Characters(1, 1).Font.Size sCap = Int(lCap * 0.85) 'Малые прописные для всех букв. oCell.Font.Size = sCap oCell.Value = UCase(oCell.Text) strTest = oCell.Value 'Большие прописные для 1-х букв слов. strTest = Application.Proper(strTest) For i = 1 To Len(strTest) If Mid(strTest, i, 1) = UCase(Mid(strTest, _ i, 1)) Then oCell.Characters(i, 1).Font.Size = lCap End If Next i End Select Next Exit Sub NoText: MsgBox "Текст в диапазоне " & Selection.Address & " отсутствует" End Sub Обработка события удаления строки или столбца Макросы EventHack и EventReset, а также вспомогательные процедуры AssignMacro, JudgeRng и DelExecute любезно предоставлены Масару Каджи (Masaru Kaji). Excel не предусматривает возможность обработки события удаления строY ки или столбца. Следующий макрос устраняет этот недостаток: при удалении Excel всемогущий Глава 13 399 строки или столбца на экран выводится сообщение, содержащее номер удаY ленной строки или удаленного столбца. Sub EventHack() AssignMacro "JudgeRng" End Sub Sub EventReset() AssignMacro "" End Sub Private Dim Dim Dim Dim Sub AssignMacro(ByVal strProc As String) lngId As Long CtrlCbc As CommandBarControl CtrlCbcRet As CommandBarControls arrIdNum As Variant '// 293: команда Удалить строки (Delete Rows) '// контекстного меню строки. '// 294: команда Удалить (Delete) контекстного '// меню столбца. '// 478: команда Удалить (Delete) меню Правка (Edit). arrIdNum = Array(293, 294, 478) For lngId = LBound(arrIdNum) To UBound(arrIdNum) Set CtrlCbcRet = CommandBars.FindControls( _ ID:=arrIdNum(lngId)) For Each CtrlCbc In CtrlCbcRet CtrlCbc.OnAction = strProc Next Set CtrlCbcRet = Nothing Next End Sub Private Sub JudgeRng() If Not TypeOf Selection Is Range Then Exit Sub With Selection If .Address = .EntireRow.Address Then Call DelExecute("строка: " & .Row, xlUp) ElseIf .Address = .EntireColumn.Address Then Call DelExecute("столбец: " & .Column, xlToLeft) Else Application.Dialogs(xlDialogEditDelete).Show End If End With End Sub Private Sub DelExecute(ByVal str, ByVal lngDerec As Long) MsgBox "Удален(а): " & str Selection.Delete lngDerec End Sub Поиск заданного текста с помощью свойства SpecialCells Макросы CheckAllCells и CheckSpecialCellsOnly любезно предосY тавлены Иваном Ф. Моалой. При поиске значения, текста или формулы в заданном диапазоне Excel проверяет каждую ячейку этого диапазона. Макрос CheckAllCells провоY 400 Часть II Автоматизация Excel дит поиск заданного текста в диапазоне A1:Z20000, проверяя каждую его ячейку. Макрос CheckSpecialCellsOnly проводит поиск заданного текста в диапазоне A1:Z20000, проверяя только ячейки, содержащие текстовые константы. По окончании поиска оба макроса выводят сведения о результатах поиска и затраченном на это времени. Sub CheckAllCells() Dim TheRange As Range Dim oCell As Range StartTime = Now Ctr = 0 Set TheRange = Range("A1:Z20000") For Each oCell In TheRange If oCell.Text = "Ваш текст" Then Ctr = Ctr + 1 End If Next oCell EndTime = Now Msg = "Проверено " & TheRange.Cells.Count & " ячеек. _ Найдено " & Ctr & " совпадений. Время поиска = " & _ Format(EndTime - StartTime, "h:mm:ss") MsgBox Msg End Sub Sub CheckSpecialCellsOnly() Dim TheRange As Range Dim oCell As Range StartTime = Now Ctr = 0 Set TheRange = Range("A1:Z20000").SpecialCells( _ xlCellTypeConstants, xlTextValues) For Each oCell In TheRange If oCell.Text = "Ваш текст" Then Ctr = Ctr + 1 End If Next oCell EndTime = Now Msg = "Проверено " & TheRange.Cells.Count & " ячеек. _ Найдено " & Ctr & " совпадений. Время поиска = " _ & Format(EndTime - StartTime, "h:mm:ss") MsgBox Msg End Sub Условное удаление строк Макрос Delete_rows_with_conditions любезно предоставлен ДенниY сом Валентайном (Dennis Wallentin), проживающим в Остерсунде, Швеция. Excel всемогущий Глава 13 401 Следующий макрос удаляет определенное число строк при выполнении усY ловия ‘‘ячейка в столбце A пуста’’. Sub Dim Dim Dim Dim Dim Delete_rows_with_conditions() wbBook As Workbook wsSheet As Worksheet rnArea As Range lnLastRow As Long, lnMoreRows As Long i As Long, j As Long, k As Long Set wbBook = ThisWorkbook Set wsSheet = wbBook.Worksheets("Условное удаление строк") Application.ScreenUpdating = False 'Определение числа строк, которые нужно удалить. j = 5 With wsSheet 'Определение номера последней строки по столбцу A. lnLastRow = .Cells(Rows.Count, 1).End(xlUp).Row + 1 'Определение номера последней строки по столбцам A, B и C. For k = 1 To 3 lnMoreRows = .Cells(Rows.Count, k).End(xlUp).Row + 1 If lnLastRow < lnMoreRows Then lnLastRow = lnMoreRows End If Next k 'Удаление строк с пустой ячейкой в столбце A. For i = lnLastRow To 1 Step -1 If IsEmpty(.Cells(i, 1)) Then .Cells(i, 1).Resize(j, 1).EntireRow.Delete End If Next i End With Application.ScreenUpdating = True End Sub Сокрытие строки формул Макрос Worksheet_SelectionChange любезно предоставлен Томом Уртисом. Если активная ячейка содержит более 50 символов, строка формул автомаY тически увеличивается в размере и перекрывает часть рабочего листа Excel. Следующий макрос скрывает строку формул при выделении ячейки, содерY жащей более 50 символов. Private Sub Worksheet_SelectionChange(ByVal Target As Range) If Target.Cells.Count > 1 Then Exit Sub On Error Resume Next If Len(Target.Text) > 50 Or Len(Target.Formula) > 50 Then 402 Часть II Автоматизация Excel Application.DisplayFormulaBar = False Else Application.DisplayFormulaBar = True End If End Sub Результат выполнения макроса Worksheet_SelectionChange показан на рис. 13.14. Рис. 13.14. Макрос Worksheet_SelectionChange скрывает строку формул при выделении ячейки, содержащей более 50 символов На закуску В этом разделе рассматривается несколько занятных макросов, которым можно найти применение в самых различных приложениях. Извлечение информации о курсах акций из Internet Процедура GetQuote любезно предоставлена Натаном П. Оливером. Следующая процедура извлекает из Internet информацию о курсе акций компании на заданную дату. Excel всемогущий Глава 13 403 Private Sub GetQuote() Dim ie As Object, lCharPos As Long, sHTML As String Dim HistDate As Date, HighVal As String, LowVal As String Dim cl As Range Set cl = ActiveCell HistDate = cl(, 0) If Intersect(cl, [c2:c65366]) Is Nothing Then MsgBox "Выберите ячейку в столбце C." Exit Sub End If If Not CBool(Len(cl(, -1))) Or Not CBool(Len(cl(, 0))) Then MsgBox "Введите символ акций и требуемую дату." Exit Sub End If Set ie = CreateObject("InternetExplorer.Application") With ie .Navigate "http://bigcharts.marketwatch.com/historical/ _ default.asp?detect=1&symbol=" & cl(, -1) & "&close_date=" & _ Month(HistDate) & "%2F" & Day(HistDate) & "%2F" & _ Year(HistDate) & "&x=31&y=26" Do While .Busy And .ReadyState <> 4 DoEvents Loop sHTML = .Document.body.innertext .Quit End With Set ie = Nothing lCharPos = InStr(1, sHTML, "High:", vbTextCompare) If lCharPos Then HighVal = Mid$(sHTML, lCharPos + 5, 15) If Not Left$(HighVal, 3) = "n/a" Then lCharPos = InStr(1, sHTML, "Low:", vbTextCompare) If lCharPos Then LowVal = Mid$(sHTML, lCharPos + 4, 15) cl.Value = (Val(LowVal) + Val(HighVal)) / 2 Else: lCharPos = InStr(1, sHTML, "Closing Price:", vbTextCompare) cl.Value = Val(Mid$(sHTML, lCharPos + 14, 15)) End If Set cl = Nothing End Sub Результат выполнения процедуры GetQuote показан на рис. 13.15. Рис. 13.15. Процедура GetQuote извлекает из Internet информацию о курсе акций ком& пании на заданную дату 404 Часть II Автоматизация Excel Вставка программного кода во вновь созданную рабочую книгу Ранее в этой главе был рассмотрен макрос, создающий отчеты для региоY нальных менеджеров в виде отдельных рабочих книг. Усложним задачу, поY требовав скопировать в новые рабочие книги программный код. Для этого обY ратимся к объектной модели Microsoft Visual Basic for Applications Extensibility, позволяющей не только импортировать в рабочую книгу модули с программY ным кодом, но и создавать код непосредственно в рабочей книге. Прежде чем выполнить какойYлибо из приведенных ниже макросов, подY ключите библиотеку Microsoft Visual Basic for Applications Extensibility 5.3, выбрав команду меню редактора Visual Basic Tools References (Сервис Ссылки) и усY тановив соответствующий флажок в открывшемся диалоговом окне. Наиболее простой способ переноса программного кода заключается в экспорте модуля и пользовательской формы из текущей рабочей книги и их последующем импорте в новую рабочую книгу. Предположим, что региоY нальному менеджеру необходимо передать отчет с данными о его регионе и 3 макроса, реализующие форматирование и печать отчета. Поместите все 3 макроса в модуль modToRegion. Предположим также, что макросы модуля modToRegion вызывают пользовательскую форму frmRegion. Следующий макрос перенесет программный код и пользовательскую форму в новую рабоY чую книгу. Sub MoveDataAndMacro() Dim WSD As Worksheet Set WSD = Worksheets("Отчет") ' Скопировать отчет в новую рабочую книгу. WSD.Copy ' Сделать новую рабочую книгу активной. ' Удалить старые копии промежуточных файлов с диска. On Error Resume Next Kill ("C:\ModToRegion.bas") Kill ("C:\frmRegion.frm") On Error GoTo 0 ' Экспортировать модуль и форму из исходной рабочей книги. ThisWorkbook.VBProject.VBComponents("ModPics").Export _ ("C:\ModToRegion.bas") ThisWorkbook.VBProject.VBComponents("frmModPics").Export _ ("C:\frmRegion.frm") ' Импортировать модуль и форму в новую рабочую книгу. ActiveWorkbook.VBProject.VBComponents.Import _ ("C:\ModToRegion.bas") ActiveWorkbook.VBProject.VBComponents.Import _ ("C:\frmRegion.frm") On Error Resume Next Kill ("C:\ModToRegion.bas") Kill ("C:\frmRegion.frm") On Error GoTo 0 End Sub Excel всемогущий Глава 13 405 Чтобы создать программный код непосредственно в рабочей книге, следует обратить внимание на два инструмента: метод Lines и метод InsertLines. Метод Lines позволяет извлечь заданное число строк из указанного модуля, а метод InsertLines — вставить код в модуль. Внимание Код, который добавляется в рабочую книгу с помощью метода InsertLines, тут же компилируется. Сбой при компиляции кода может привести к ошибке общего нарушения защиты (General Protection Failure, GPF), чего нужно всячески избегать. Следующий макрос копирует весь код модуля ЭтаКнига исходной рабочей книги в модуль ЭтаКнига новой рабочей книги. Sub MoveDataAndMacro() Dim WSD As Worksheet Dim WBN As Workbook Set WSD = Worksheets("Отчет") ' Скопировать отчет в новую рабочую книгу. WSD.Copy ' Сделать новую рабочую книгу активной. Set WBN = ActiveWorkbook ' Скопировать обработчики событий уровня рабочей книги. Set WBCodeMod1 = ThisWorkbook.VBProject.VBComponents( _ "ЭтаКнига").CodeModule Set WBCodeMod2 = WBN.VBProject.VBComponents( _ "ЭтаКнига").CodeModule WBCodeMod2.InsertLines 1, WBCodeMod1.Lines(1, _ WBCodeMod1.CountOfLines) Следующий шаг В следующей главе рассматриваются WebYзапросы, позволяющие импорY тировать данные из Internet в приложения Excel. Глава 14 Âçàèìîäåéñòâèå ñ Internet В этой главе рассматривается авY томатизация WebYзапросов, целью которых является помещение инY формации из Internet в электронную таблицу, и наоборот. Извлечение данных из Internet На рис. 14.1 показана страница WebYсайта Finance.Yahoo.com, содерY жащая сведения о курсах акций разY личных компаний. Понимая всю важность электронY ных таблиц, разработчики поместили внизу страницы ссылку, позволяюY щую загрузить данные, которые были получены в результате WebYзапроса, в файле формата CSV. Создание WebLзапроса с помощью пользовательского интерфейса Excel Рассмотрим создание WebYзапроса вручную с помощью пользовательского интерфейса Excel. Запустите WebY обозреватель и откройте страницу, соY держащую требуемую информацию. Ниже приведен адрес URL страницы, изображенной на рис. 14.1: http://finance.yahoo.com/q/cq?d= v1&s=PSO,+SJM,+KO,+MSFT,+CSCO, +INTC 14 Извлечение данных из Internet........................................... 407 Извлечение данных из Internet в режиме реального времени.......................................... 413 Анализ данных, извлеченных из Internet.......................................414 Размещение данных на WebL странице.........................................418 Следующий шаг........................... 425 408 Часть II Автоматизация Excel Рис. 14.1. Web&сайт Finance.Yahoo.com позволяет получить информацию о текущих курсах акций Откройте Excel и выберите на рабочем листе пустую область, предназначенY ную для вставки данных из Internet. Выберите команду меню Данные Импорт внешних данных Создать веб-запрос (Data Import External Data New Web Query). Скопируйте адрес WebYстраницы из окна обозревателя в поле Адрес (Address) открывшегося диалогового окна Создание веб-запроса (New Web Query) и щелкните на кнопке Пуск (Go). Загрузившаяся WebYстраница будет содержать значки с изображением черной стрелки в желтом квадрате, обознаY чающие левые верхние углы имеющихся на странице таблиц. При подведении указателя мыши к такому значку на экране появится рамка, ограничивающая соответствующую таблицу. Щелкните на значке, чтобы выбрать таблицу. В реY зультате этого стрелка сменится флажком, а цвет фона станет зеленым вместо желтого (рис. 14.2). Щелкните на кнопке Импорт (Import), а затем YYYY на кнопке OK в открывY шемся диалоговом окне Импорт данных (Import Data). Через несколько сеY кунд данные из Internet будут помещены на рабочий лист Excel, как показано на рис. 14.3. Взаимодействие с Internet Глава 14 409 Рис. 14.2. Выберите требуемую таблицу, щелкнув на соответствующем значке с изображе& нием черной стрелки в желтом квадрате Рис. 14.3. Данные успешно скопированы с Web&страницы на рабочий лист Excel Обновление существующего WebLзапроса с помощью VBA Чтобы обновить все WebYзапросы на текущем рабочем листе, выполните следующий код VBA, назначив его кнопке или комбинации клавиш: Sub RefreshAllWebQueries() Dim QT As QueryTable For Each QT In ActiveSheet.QueryTables Application.StatusBar = "Обновление " & QT.Connection QT.Refresh 410 Часть II Автоматизация Excel Next QT Application.StatusBar = False End Sub Создание WebLзапроса с помощью VBA Рассмотренный ранее пример имеет один существенный недостаток YYYY при изменении портфеля акций соответствующим образом следует изменить и жестко заданный WebYзапрос. Создать WebYзапрос ‘‘на лету’’ совсем не сложно. Для этого нужно сформиY ровать так называемую строку подключения, пример которой показан ниже: URL;http://finance.yahoo.com/q/cq?d=v1&s=PSO,+SJM,+KO,+MSFT,+CSCO,+INTC При формировании строки подключения не обойтись без символа соедиY нения строк, реализующего ‘‘сцепление’’ основной части строки подключеY ния с заданными пользователем символами акций. На рис. 14.4 показан простейший интерфейс создания WebYзапросов. Рис. 14.4. Введите символы акций в ячейки столбца A и щелкните на кнопке Узнать курсы акций Введите символы акций в ячейки столбца A и щелкните на кнопке Узнать курсы акций, чтобы выполнить макрос CreateNewQuery. Макрос CreateNewQuery создает строку подключения, объединяя ее осY новную часть с символом акций, размещенном в ячейке A2: ConnectString = "URL;http://finance.Yahoo.com/q/cq?d=v1&s=" & _ WSD.Cells(i, 1).Value По мере обнаружения новых символов акций макрос добавляет в конец существующей строки подключения комбинацию ,+ и символ акции: ConnectString = ConnectString & ",+" & WSD.Cells(i, 1).Value Взаимодействие с Internet Глава 14 После формирования строки подключения макрос удаляет все ранее созY данные WebYзапросы, выполняет новый WebYзапрос и помещает его результат на рабочий лист Вспомогательный лист. Следует отметить, что при выполнении WebYзапроса параметр BackgroundRefresh метода Refresh устанавливается равным False. Это поY зволяет приостановить выполнение макроса на время, требующееся для изY влечения информации из Internet. После получения данных макрос присваивает имя соответствующему диаY пазону ячеек рабочего листа Вспомогательный лист и переносит извлеY ченную из Internet информацию на рабочий лист Портфель акций с помоY щью формул ВПР (VLOOKUP). Sub CreateNewQuery() Dim WSD As Worksheet Dim WSW As Worksheet Dim QT As QueryTable Set WSD = Worksheets("Портфель акций") Set WSW = Worksheets("Вспомогательный лист") ' Создание строки подключения. FinalRow = WSD.Cells(65536, 1).End(xlUp).Row For i = 2 To FinalRow Select Case i Case 2 ConnectString = _ "URL;http://finance.Yahoo.com/q/cq?d=v1&s=" & WSD.Cells(i, 1).Value Case Else ConnectString = _ ConnectString & ",+" & WSD.Cells(i, 1).Value End Select Next i ' Удаление существующих запросов. For Each QT In WSW.QueryTables QT.Delete Next QT WSW.Select WSW.Cells.Clear ' Создание нового запроса. Set QT = WSW.QueryTables.Add(Connection:=ConnectString, _ Destination:=WSW.Range("A1")) With QT .Name = "portfolio" .FieldNames = True .RowNumbers = False .FillAdjacentFormulas = False .PreserveFormatting = True .RefreshOnFileOpen = False .BackgroundQuery = False .RefreshStyle = xlInsertDeleteCells .SavePassword = False 411 412 Часть II Автоматизация Excel .SaveData = True .AdjustColumnWidth = True .RefreshPeriod = 0 .WebSelectionType = xlSpecifiedTables .WebFormatting = xlWebFormattingNone .WebTables = "20" .WebPreFormattedTextToColumns = True .WebConsecutiveDelimitersAsOne = True .WebSingleBlockTextImport = False .WebDisableDateRecognition = False .WebDisableRedirections = False End With ' Выполнение запроса. QT.Refresh BackgroundQuery:=True ' Создание именованного диапазона, ' содержащего результаты запроса. WSW.Cells(1, 1).Resize(FinalRow, 6).Name = "WebInfo" ' Перенесение полученной информации ' на рабочий лист "Портфель акций". RowCount = FinalRow - 1 ' В англоязычной версии Excel: 'WSD.Cells(2, 2).Resize(RowCount, 1).FormulaR1C1 = _ "=VLOOKUP(RC1,WebInfo,3,False)" WSD.Cells(2, 2).Resize(RowCount, 1).FormulaR1C1Local = _ "=ВПР(RC1;WebInfo;3;ЛОЖЬ)" ' В англоязычной версии Excel: 'WSD.Cells(2, 3).Resize(RowCount, 1).FormulaR1C1 = _ "=VLOOKUP(RC1,WebInfo,4,False)" WSD.Cells(2, 3).Resize(RowCount, 1).FormulaR1C1Local = _ "=ВПР(RC1;WebInfo;4;ЛОЖЬ)" ' В англоязычной версии Excel: 'WSD.Cells(2, 4).Resize(RowCount, 1).FormulaR1C1 = _ "=VLOOKUP(RC1,WebInfo,5,False)" WSD.Cells(2, 4).Resize(RowCount, 1).FormulaR1C1Local = _ "=ВПР(RC1;WebInfo;5;ЛОЖЬ)" ' В англоязычной версии Excel: 'WSD.Cells(2, 5).Resize(RowCount, 1).FormulaR1C1 = _ "=VLOOKUP(RC1,WebInfo,6,False)" WSD.Cells(2, 5).Resize(RowCount, 1).FormulaR1C1Local = _ "=ВПР(RC1;WebInfo;6;ЛОЖЬ)" WSD.Cells(2, 6).Resize(RowCount, 1).Value = Time WSD.Cells(2, 6).Resize(RowCount, 1).NumberFormat = "h:m:s" WSD.Select MsgBox "Подождите, пока будут обновлены данные" End Sub Результат выполнения макроса CreateNewQuery показан на рис. 14.5. Взаимодействие с Internet Глава 14 413 Рис. 14.5. В результате выполнения макроса CreateNewQuery на рабочий лист Портфель акций помещается только существенная информация. Необработанный результат Web&запроса можно увидеть на листе Вспомогательный лист Извлечение данных из Internet в режиме реального времени В Internet существует много служб, позволяющих доставлять данные непоY средственно на рабочий лист Excel в режиме реального времени. Обычно для доставки данных используется технология DDE. Вызов типичной DDEYслужбы реального времени осуществляется посредY ством формулы, идентифицирующей внешний исполняемый файл (.exe). Чтобы передать удаленной программе входные данные, используется символ перенаправления (|). На рис. 14.6 показан пример вызова удаленной программы MktLink.exe, возвращающей курс акций с символом AA. Рис. 14.6. Вызов типичной DDE&службы ре& ального времени Наблюдать за изменением курса акций в режиме реального времени, несоY мненно, интересно. Однако еще более интересно и полезно анализировать получаемую информацию с целью выявления трендов. 414 Часть II Автоматизация Excel Анализ данных, извлеченных из Internet VBA содержит метод OnTime, позволяющий выполнить любую процедуY ру в определенный момент времени или по прошествии заданного периода времени. Ниже приведен код VBA, извлекающий данные из Internet каждый час на протяжении всего рабочего дня. Sub ScheduleTheDay() Application.OnTime EarliestTime:=TimeValue("8:00 AM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("9:00 AM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("10:00 AM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("11:00 AM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("12:00 AM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("1:00 PM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("2:00 PM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("3:00 PM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("4:00 PM"), _ Procedure:="CaptureData" Application.OnTime EarliestTime:=TimeValue("5:00 PM"), _ Procedure:="CaptureData" End Sub Sub CaptureData() Dim WSQ As Worksheet Set WSQ = Worksheets("MyQuery") ' Выполнить запрос. WSQ.Range("A2").QueryTable.Refresh BackgroundQuery:=False ' Приостановить выполнение макроса. Application.Wait (Now + TimeValue("0:00:05")) ' Скопировать результаты запроса. NextRow = WSQ.Range("A65536").End(xlUp).Row + 1 WSQ.Range("A2:B2").Copy WSQ.Cells(NextRow, 1) ' Зафиксировать дату и время. WSQ.Cells(NextRow, 2).Value = WSQ.Cells(NextRow, 2).Value End Sub Условия выполнения метода OnTime Метод OnTime выполняется в заданный момент времени при условии преY бывания Excel в режиме готовности (Ready), копирования (Copy), вырезания (Cut) или поиска (Find). Excel не сможет выполнить макрос CaptureData в 08:00, если в это время какаяYлибо ячейка рабочего листа находится в режиме редактирования. Взаимодействие с Internet Глава 14 415 Обратите внимание, что в предыдущем примере было задано только наY чальное время выполнения макроса CaptureData. Это означает, что Excel будет терпеливо ожидать возвращения в режим готовности, чтобы выполнить указанный макрос. Предположим, что пользователь начал редактировать ячейку в 07:59, а заY тем был вызван на экстренное совещание, продолжавшееся до 10:30. За это время Excel должен был выполнить макрос CaptureData три раза: в 08:00, 09:00 и 10:00. Вернувшись на свое рабочее место и нажав клавишу <Enter>, чтобы выйти из режима редактирования, пользователь обнаружит, что три первых WebYзапроса были выполнены между 10:30 и 10:31. Определение временного окна для выполнения макроса Чтобы обойти описанную выше проблему, можно определить временные рамки для выполнения макроса. Следующий код указывает Excel на необхоY димость выполнения макроса CaptureData в промежутке времени между 08:00 и 08:05. Если на протяжении всего этого периода Excel будет пребывать в режиме редактирования, выполнение назначенного задания отменяется. Application.OnTime EarliestTime:=TimeValue("8:00 AM"), _ Procedure:="CaptureData", LatestTime:=TimeValue("08:05 AM") Отмена назначенного задания Отменить ранее назначенное задание весьма непросто, так как для этого необходимо знать точное время выполнения макроса. Чтобы отменить назнаY ченное задание, вызовите метод OnTime, установив значение параметра Schedule равным False. Ниже приведен код, отменяющий выполнение макроса CaptureData, назначенное на 11:00. Sub CancelEleven() Application.OnTime EarliestTime:=TimeValue("11:00 AM"), _ Procedure:="CaptureData", _ Schedule:=False End Sub Обратите внимание, что расписание, заданное с помощью метода OnTime, существует на уровне выполняющейся копии Excel. Другими словами, закрыY тие рабочей книги не отменяет текущее расписание, если копия Excel остается открытой. Рассмотрим приведенную ниже последовательность действий. 1. Открытие Excel в 07:30. 2. Открытие рабочей книги Schedule.xls и запуск макроса, назначаюY щего выполнение процедуры на 08:00. 3. Закрытие рабочей книги Schedule.xls. 4. Открытие новой рабочей книги и начало ввода данных. В 08:00 Excel откроет рабочую книгу Schedule.xls и выполнит назнаY ченное задание. Естественно, это может стать полной неожиданностью для 416 Часть II Автоматизация Excel пользователя. При большом числе назначенных заданий рекомендуется отY крывать две копии Excel, одну из которых использовать для выполнения задаY ний, а другую YYYY для работы. Отмена всех назначенных заданий Чтобы отменить все назначенные задания, закройте Excel с помощью коY манды меню Файл Выход (File Exit). Обычно к подобному действию приY бегают при необходимости отмены большого числа автоматически назначенY ных заданий с заранее неизвестным точным временем выполнения. Выполнение макроса по прошествии заданного периода времени Excel позволяет выполнить макрос по прошествии заданного периода вреY мени. Например, макрос CaptureData будет запущен через 2 мин и 30 с поY сле выполнения следующего кода. Sub ScheduleAnything() WaitHours = 0 WaitMin = 2 WaitSec = 30 NameOfScheduledProc = "CaptureData" ' Определение времени выполнения назначенного задания. NextTime = Time + TimeSerial(WaitHours, WaitMin, WaitSec) ' Создание назначенного задания. Application.OnTime EarliestTime:=NextTime, _ Procedure:=NameOfScheduledProc End Sub Периодическое выполнение макроса через определенные промежутки времени Предположим, что некий макрос нужно выполнять каждые 2 мин. Если в заданное время Excel находится в режиме редактирования, выполнение макY роса, назначенное на это время, следует отменить. Одно из наиболее очевидных решений поставленной задачи заключается в рекурсивном планировании макросом своего собственного выполнения. Sub ScheduleRepeatedly() WaitHours = 0 WaitMin = 2 WaitSec = 0 NameOfThisProcedure = "ScheduleRepeatedly" NameOfScheduledProc = "CaptureData" ' Определение времени выполнения назначенного задания. NextTime = Time + TimeSerial(WaitHours, WaitMin, WaitSec) Взаимодействие с Internet Глава 14 417 ' Создание назначенного задания. Application.OnTime EarliestTime:=NextTime, _ Procedure:=NameOfThisProcedure ' Выполнить макрос NameOfScheduledProc ' (в данном случае - макрос "CaptureData"). Application.Run NameOfScheduledProc End Sub Подобный подход имеет неоспоримые преимущества. ВоYпервых, он поY зволяет не планировать огромное число заданий наперед. В каждый момент времени существует только одно назначенное задание. ВоYвторых, чтобы преY кратить периодическое выполнение макроса CaptureData каждые 2 мин, достаточно закомментировать строку Application.OnTime EarliestTime:=NextTime, Procedure:=NameOfThisProcedure и дождаться поY следнего выполнения этого макроса. Практикум Отслеживание размера национального долга США Рассмотрим рабочий лист, показанный на рис. 14.7. Рис. 14.7. Ячейка A2 содержит результат Web&запро& са, а ячейка B2 — текущие дату и время. Цель мак& роса DebtClock — каждые 15 с получать из Internet данные о размере национального долга США и сохранять их на рабочем листе Национальный долг США, начиная с 6&й строки Ячейка A2 содержит результаты Web&запроса по адресу: http://brillig.com/ debt_clock/, а ячейка B2 — функцию ТДАТА (NOW). Макрос DebtClock предназначен для отслеживания размера национального долга США. Следующий код выполняет Web&запрос, сохраняет полученный ре& зультат на рабочем листе и планирует свое выполнение через 15 с. Sub DebtClock() Dim WSQ As Worksheet Set WSQ = Worksheets("Национальный долг США") ' Ячейка A2 содержит Web-запрос по адресу: ' http://brillig.com/debt_clock WaitSec = 15 418 Часть II Автоматизация Excel NameOfThisProcedure = "DebtClock" ' Определение времени выполнения назначенного задания. NextTime = Time + TimeSerial(0, 0, WaitSec) ' Создание назначенного задания. Application.OnTime EarliestTime:=NextTime, _ Procedure:=NameOfThisProcedure ' Выполнение Web-запроса. WSQ.Range("A2").QueryTable.Refresh BackgroundQuery:=False ' Приостановка выполнения макроса. Application.Wait (Now + TimeValue("0:00:05")) ' Копирование результатов запроса. NextRow = WSQ.Range("A65536").End(xlUp).Row + 1 WSQ.Range("A2:B2").Copy WSQ.Cells(NextRow, 1) ' Фиксирование даты и времени. WSQ.Cells(NextRow, 2).Value = WSQ.Cells(NextRow, 2).Value End Sub Запустив макрос и подождав несколько минут, можно увидеть, что каждую ми& нуту национальный долг США увеличивается примерно на 2 млн долларов (рис. 14.8). Рис. 14.8. Спустя несколько минут после запуска макроса DebtClock на рабочем листе будут содер& жаться данные, достаточные для анализа темпов изменения национального долга США Размещение данных на WebLстранице Ранее в этой главе были рассмотрены различные способы извлечения инY формации из Internet. Вместе с тем, Excel поддерживает и обратную операY цию YYYY размещение данных рабочего листа на WebYстранице. Взаимодействие с Internet Глава 14 419 В главе 12, ‘‘Сводные таблицы’’, был описан макрос, создающий отчеты для региональных менеджеров компании. Вместо отправки отчетов по факсу или электронной почте их можно сохранить в формате HTML и разместить на корпоративном сайте компании. Рассмотрим отчет, показанный на рис. 14.9. Рис. 14.9. Отчет, сгенерированный макросом из главы 12 Чтобы сохранить содержимое рабочего листа в формате HTML с помощью пользовательского интерфейса Excel, выберите команду меню Файл Сохранить как (File Save As). Выбрав формат сохранения Веб-страница (Web Page), убедитесь, что флаY жок Добавить интерактивность (Add interactivity) сброшен (рис. 14.10). В проY тивном случае созданную WebYстраницу можно будет просмотреть только при условии наличия на компьютере установленной копии Excel. Рис. 14.10. Сохраняя рабочий лист в формате Web&страницы, проверьте, чтобы флажок Добавить интерактивность (Add interactivity) был сброшен — Web& страница будет доступна для гораздо большего числа пользователей 420 Часть II Автоматизация Excel Файл, полученный в результате сохранения рабочего листа в формате HTML, можно просмотреть с помощью любого обозревателя Web, как покаY зано на рис. 14.11. Рис. 14.11. Excel успешно справляется с задачей сохранения рабочего листа в формате HTML Новая концепция сохранения данных в формате WebYстраницы, представY ленная Microsoft, предполагает возможность восстановления рабочего листа на основе полученного HTMLYфайла. Платой за подобную гибкость является чрезмерный объем генерируемого HTMLYкода. В частности, для представлеY ния информации, показанной на рис. 14.11, достаточно 457 байт. Будучи пеY реведенной в формат HTML, эта же информация занимает уже 9105 байт. Создание WebLстраниц с помощью VBA Лишенные возможности сохранения рабочего листа в формате WebY страницы, пользователи предыдущих версий Excel были вынуждены создавать HTMLYкод с помощью VBA. Следует отметить, что этот метод имеет одно весьY ма существенное преимущество YYYY он позволяет создавать ‘‘полноценные’’ WebY страницы (содержащие логотип компании, навигационные панели и т.п.), готоY вые к немедленному размещению на сайте. Шаблон типичной WebYстраницы содержит код для отображения логотипа компании и навигационных панелей, данные этой страницы и код, заверY шающий HTMLYфайл. Создайте подобный шаблон, поместив вместо данных страницы текст ‘‘ДАННЫЕ WEBYСТРАНИЦЫ’’, и просмотрите полученный код с помощью приложения Блокнот (Notepad), как показано на рис. 14.12. Взаимодействие с Internet Глава 14 421 Рис. 14.12. Шаблон типичной Web&страницы можно условно разделить на три части: код для отображения логотипа компании и навигационных панелей; данные Web&страницы; и код, за& вершающий HTML&файл Анализ кода типичной WebYстраницы наталкивает на мысль о необходиY мости поэтапного создания HTMLYкода с помощью VBA. 1. Создайте код для отображения логотипа компании и навигационных панелей. 2. Создайте код, размещающий на WebYстранице данные с рабочего листа Excel. 3. Создайте код, завершающий HTMLYфайл. Применение Excel в качестве системы управления содержимым Создадим простую систему управления содержимым, генерирующую WebY страницы на основе имеющихся данных Excel. На рис. 14.13 показано содержимое типичной базы данных. Разработаем код VBA, генерирующий WebYстраницу на основе данных, поY казанных на рис. 14.13, и на основе HTMLYкода, показанного на рис. 14.12. 422 Часть II Автоматизация Excel Рис. 14.13. Базы данных, подобные этой, поддерживаются огромным числом всевозможных компаний Добавим к рабочей книге Excel, содержащей сведения из базы данных, два рабочих листа. Первый рабочий лист (назовем его Top) будет содержать код, отображающий логотип компании и навигационные панели. Второй рабочий лист (назовем его Bottom) будет содержать код, завершающий HTMLYфайл (рис. 14.14). Конечным результатом выполнения макроса WriteMembershipHTML является HTMLYфайл sampledirectory.html. Первым делом макрос копируY ет в этот файл HTMLYкод, размещенный на рабочем листе Top. На следующем этапе макрос WriteMembershipHTML экспортирует сведения из базы данных в Рис. 14.14. Рабочий лист файл sampledirectory.html. Bottom содержит деск& Наконец, макрос WriteMembershipHTML копируY рипторы, необходимые ет в файл sampledirectory.html HTMLYкод, разY для завершения HTML& файла мещенный на рабочем листе Bottom. Sub WriteMembershipHTML() ' Этот макрос создает Web-страницу. Dim WST As Worksheet Dim WSB As Worksheet Dim WSM As Worksheet Set WSB = Worksheets("Bottom") Set WST = Worksheets("Top") Set WSM = Worksheets("База данных") ' Определение пути к текущей рабочей книге. MyPath = ThisWorkbook.Path LineCtr = 0 Взаимодействие с Internet Глава 14 423 FinalT = WST.Cells(65536, 1).End(xlUp).Row FinalB = WSB.Cells(65536, 1).End(xlUp).Row FinalM = WSM.Cells(65536, 1).End(xlUp).Row MyFile = "sampledirectory.html" ThisFile = MyPath & Application.PathSeparator & MyFile ThisHostFile = MyFile ' Удалить существующую Web-страницу. On Error Resume Next Kill (ThisFile) On Error GoTo 0 ' Создать заголовок Web-страницы. ThisTitle = "<Title>Список членов организации ""Lake _ Township Chamber of Commerce""</Title>" WST.Cells(3, 2).Value = ThisTitle ' Открыть файл для записи. Open ThisFile For Output As #1 ' Скопировать HTML-код с рабочего листа "Top". For j = 2 To FinalT Print #1, WST.Cells(j, 2).Value Next j ' Экспортировать сведения из базы данных в HTML-файл. For j = 2 To FinalM ' Название компании, выделенное полужирным шрифтом. Print #1, "<b>" & WSM.Cells(j, 1).Value & "</b><br>" ' Адрес компании. Print #1, WSM.Cells(j, 2).Value & "<br>" ' Город, штат и Zip-код. Addr = WSM.Cells(j, 3) & " " & WSM.Cells(j, 4) & " " _ & WSM.Cells(j, 5) Print #1, Addr & "<br>" ' Номер телефона и 2 символа разрыва строки. Print #1, WSM.Cells(j, 6).Value & "<br><br>" Next j ' 20 символов разрыва страницы. For j = LineCtr To 20 Print #1, "<br>" Next j ' Дата последнего изменения Web-страницы. Print #1, "<br>" Print #1, "Последнее изменение " & Format(Date, "mmmm dd, _ yyyy") & " " & Format(Time, "h:mm AM/PM") ' Скопировать HTML-код с рабочего листа "Bottom". For j = 2 To FinalB Print #1, WSB.Cells(j, 2).Value Next j ' Закрыть файл. Close #1 424 Часть II Автоматизация Excel Application.StatusBar = False Application.CutCopyMode = False MsgBox "Web-страница успешно создана" End Sub Результат выполнения макроса WriteMembershipHTML показан на рис. 14.15. Рис. 14.15. Web&страница, созданная с помощью макроса WriteMembershipHTML Подобная система управления содержимым имеет много преимуществ. Сотруднику, поддерживающему базу данных, достаточно щелкнуть на кнопке, чтобы обновить содержимое WebYстраницы. С другой стороны, WebYдизайнеру, решившему изменить внешний вид шаблона WebYстраницы, достаточно скопировать обновленный HTMLYкод на рабочие листы Top и Bottom. Также следует отметить, что размер WebYстраницы, созданной с помощью макроса WriteMembershipHTML, в несколько раз меньше размера WebY страницы, сгенерированной с помощью встроенного средства сохранения файлов Excel. Рассмотренная система управления содержимым может быть с успехом взята за основу более сложной системы, создающей не одну, а несколько деY сятков различных WebYстраниц. Взаимодействие с Internet Глава 14 425 Загрузка WebLстраницы на FTPLсервер После создания WebYстраницы ее нужно загрузить с жесткого диска комY пьютера на FTPYсервер. К сожалению, далеко не все пользователи Excel могут справиться с этой задачей. Воспользуемся бесплатным FTPYклиентом WCL_FTP, созданным Кеном Андерсоном (Ken Anderson), по адресу: http://www.pacific.net/~ken/ software/. Разместите файл WCL_FTP.exe в корневой папке жесткого дисY ка C: и примените следующий код для загрузки созданной ранее WebY страницы на ваш FTPYсервер. Sub DoFTP(fname, pathfname) ' Скопируйте файл wcl_ftp.exe в корневую папку диска C:. ' FTP-клиент WCL_FTP находится по адресу: http://www.pacific.net/~ken/software/. ' ' ' ' ' ' ' Создать командную строку со следующим синтаксисом: WCL_FTP.exe "Заголовок окна" АдресСервераFTP ИмяПользователя Пароль КаталогНаСервереFTP ИмяФайлаНаСервереFTP ЛокальноеИмяФайла Операция (get или put) РежимПередачи (0 - Ascii, 1 - двоичный) Журнал (0 - нет, 1 - да) ФоновыйРежим (0 - нет, 1 - да) ЗакрытьПоОкончанииРаботы (0 - нет, 1 - да) ПассивныйРежим (0 - нет, 1 - да) ЖурналОшибок (0 - нет, 1 - да) s = """c:\wcl_ftp.exe"" " _ & """Загрузка файла на FTP-сервер"" " _ & "ftp.АдресСервераFTP.com ИмяПользователя Пароль _ КаталогНаСервереFTP " _ & fname & " " _ & """" & pathfname & """ " _ & "put " _ & "0 0 0 1 1 1" Shell s, vbMinimizedNoFocus End Sub Следующий шаг Следующая глава посвящена передаче данных между приложениями с поY мощью XML. Использование XML намного эффективнее WebYзапросов. НеY которые сайты (например, Amazon.com) предлагают такую услугу уже сегодня. Глава 15 Ïîääåðæêà XML â ïðîôåññèîíàëüíîì âûïóñêå Excel 2003 Одним из наиболее существенных нововведений профессионального выY пуска Office 2003 является улучшенная поддержка работы с данными в формаY те XML. Для тех, кто еще не сталкиY вался с XML в своей повседневной работе, в конце главы имеется пракY тикум, демонстрирующий получение данных в формате XML с WebYсайта Amazon.com. Введение в XML Исходный код WebYстраницы ‘‘усыпан’’ так называемыми HTMLY тегами. Например, в самом начале страницы обычно присутствует тег, определяющий ее заголовок: <TITLE>Заголовок Webстраницы</TITLE> Типичная WebYстраница содерY жит также теги, определяющие абзаY цы, таблицы, строки в таблицах и т.п. Синтаксис тегов описан в специфиY кации HTML. XML имеет много общего с HTML. Одно из ключевых отличий XML от HTML состоит в том, что XML позвоY ляет определять любой тег. Ниже приY 15 Введение в XML ............................427 Правила XML ................................ 428 Универсальный формат файлов........................................... 429 XML набирает обороты ............. 429 Схемы и сопоставления XML.... 429 Сохранение и считывание содержимого рабочей книги Excel в формате XML.................... 431 Следующий шаг........................... 438 428 Часть II Автоматизация Excel веден код XML, содержащий информацию о заключенных за день сделках (для создания подобного кода можно воспользоваться приложением Блокнот (Notepad)): <TodaysOrders> <SalesOrder> <Customer>BCA Co</Customer> <Address>123 North</Address> <City>Stow</City> <State>OH</State> <Zip>44224</Zip> <ItemSKU>23456</ItemSKU> <Quantity>500</Quantity> <UnitPrice>21.75</UnitPrice> </SalesOrder> <SalesOrder> <Customer>DEF Co</Customer> <Address>234 Carapace Lane</Address> <City>South Bend</City> <State>IN</State> <Zip>44685</Zip> <ItemSKU>34567</ItemSKU> <Quantity>20</Quantity> <UnitPrice>50.00</UnitPrice> </SalesOrder> </TodaysOrders> Правила XML Ниже приведено несколько простых правил, подчеркивающих отличия между XML и HTML. Каждый элемент данных должен быть заключен в идентичные теги. Поскольку теги XML чувствительны к регистру, запись <TagName>Data</Tagname> некорректна. Примером правильной заY писи является запись <TagName>Data</TagName>. XMLYфайл должен начинаться и заканчиваться так называемым корY невым тегом. В каждом XMLYфайле может существовать только один корневой тег. В рассмотренном выше примере таким тегом является тег <TodaysOrders>. XML допускает наличие пустых тегов, отличающихся символом косой черты после названия тега. Например, чтобы указать на отсутствие в данной записи информации о ZipYкоде, используйте тег <Zip/>. При вложении внутренние теги должны закрываться раньше, чем внешY ние теги. Например, в HTML допускается такая запись: <b>XML - это очень <i>круто</b></i>. В XML подобная запись была бы некорY ректной. Примером корректного закрытия тегов в XML является запись <Item><a>data</a></Item>. Поддержка XML в профессиональном выпуске Excel 2003 Глава 15 429 Универсальный формат файлов На протяжении многих лет универсальным форматом файлов считался формат CSV. Практически все приложения для работы с электронными табY лицами обладали возможностью сохранения и считывания данных в виде знаY чений с разделителямиYзапятыми. На сегодняшний момент наиболее вероятY ным претендентом на звание универсального является формат файлов XML. При обмене данными в формате CSV обе стороны должны иметь одинакоY вое представление об их структуре. В рассмотренном выше примере поле ZipY кода имеет порядковый номер 5, а поле кода SKU — порядковый номер 6. НаY рушение очередности следования этих полей может привести к нежелательY ным последствиям. На рис. 15.1 показан результат открытия в Excel типичного CSVYфайла. Рис. 15.1. Формат CSV не содержит никакой информации о структуре данных XML набирает обороты Формат XML предлагает гораздо более широкие возможности. На рис. 15.2 показан результат открытия в Excel XMLYфайла, содержащего сведения о сделках, заключенных в продолжение рабочего дня. На основе информации, хранящейся в XMLYфайле, Excel отображает загоY ловки столбцов и даже может вывести так называемую схему данных. Схема хранится в отдельном файле и описывает столбцы данных и существующие между ними отношения. Excel поддерживает сохранение данных в формате XML. Добавьте новые записи в XMLYсписок и выберите команду меню Файл Сохранить как (File Save As). Из раскрывающегося списка Тип файла (Save as type) выберите знаY чение XML-данные (XML Data) и щелкните на кнопке Сохранить (Save), как показано на рис. 15.3. Схемы и сопоставления XML В предыдущем разделе было рассмотрено считывание, редактирование и сохранение XMLYданных с помощью Excel. Существует два дополнительных типа файлов, предназначенных для рабоY ты с XMLYданными YYYY файл схемы и файл сопоставления. В то время как XMLYфайл содержит данные и имена полей, схема XML опY ределяет взаимоотношения между полями и правила проверки корректности данных (например, значение, представляющее ZipYкод, должно состоять из пяти цифр). Схема XML хранится в файле с расширением .XSD. 430 Часть II Автоматизация Excel Рис. 15.2. Формат XML содержит сведения о структуре данных, корректно интерпретируемые Excel Рис. 15.3. Excel поддерживает два способа сохранения XML&данных: в виде элек& тронной таблицы XML и в виде обычного XML&файла Сопоставление описывает способ отображения XMLYданных на документ Excel. В файле сопоставления можно определить поля XMLYфайла, которые Поддержка XML в профессиональном выпуске Excel 2003 Глава 15 431 будут отображены на рабочем листе. Схеме XML могут соответствовать неY сколько сопоставлений, каждое из которых будет определять некоторое предY ставление одних и тех же данных. Сопоставление XML хранится в файле с расширением .XLS. XSDY и XLSYфайлы обычно поставляются вместе с XMLYданными. При неY обходимости, Excel может вывести схему XML на основе существующего XMLY файла. Откройте созданный ранее файл TodaysOrders.xml и введите в окне Immediate (Быстрое выполнение) редактора Visual Basic следующую строку: Print ActiveWorkbook.XmlMaps(1).Schemas(1).XML Скопируйте полученный результат в Блокнот (Notepad) и сохраните его в файле с именем TodaysOrders.xsd. Сгенерированная схема XML с добавY ленными для повышения удобочитаемости переносами строк и отступами поY казана на рис. 15.4. Рис. 15.4. Схема XML, выведенная на основе содержимого файла TodaysOrders.xml В отличие от схемы, Excel не поддерживает автоматического генерироY вания сопоставления XML. Более подробную информацию о создании XSLYфайлов вы сможете найти по адресу: http://www.mrexcel.com/ tip064.shtml. Сохранение и считывание содержимого рабочей книги Excel в формате XML Microsoft рассматривает формат XML как стандартный формат файлов доY кументов Office. Это означает, что, теоретически, любая программа, способY ная сохранять данные в формате XML, может быть использована для создания файла Excel. 432 Часть II Автоматизация Excel На рис. 15.5 показан результат сохранения и последующего считывания соY держимого файла Excel в формате XML. Рис. 15.5. Формат XML можно применять в качестве стандартного формата файлов Excel 2003 На рис. 15.5 слева показан исходный файл Excel, содержащий стилевое форматирование и диаграмму, а на рис. 15.5 справа YYYY результат открытия XMLYфайла, созданного при сохранении исходного файла в виде электронной таблицы XML. При сохранении файла рабочей книги в виде электронной табY лицы XML Excel предупреждает о невозможности сохранения диаграмм и проектов VBA. Во всем остальном полученный файл XML ничем не отличаетY ся от исходного файла Excel. Фрагмент XMLYкода, сгенерированного при сохранении файла Excel в виY де электронной таблицы XML, представлен на рис. 15.6. Поддержка XML в профессиональном выпуске Excel 2003 Глава 15 433 Рис. 15.6. Придерживаясь спецификаций Microsoft, любая программа, способная сохранять данные в формате XML, может быть использована для создания файла Excel Практикум Извлечение XML-данных с сайта Amazon.com Рассмотрим пример создания VBA&макроса, извлекающего XML&данные с сайта Amazon.com. Прежде чем приступить к созданию макроса, загрузите с сайта Amazon.com набор инструментальных средств разработки, выполнив следующие действия. 1. С помощью обозревателя Internet посетите страницу http://amazon.com/ webservices. 2. Щелкните на ссылке E-Commerce Service (Служба электронной коммерции), расположенной на левой навигационной панели. 3. В разделе Other Versions Available (Другие версии) щелкните на ссылке Here (Здесь) для загрузки файла kit.zip на свой компьютер. 4. Распакуйте файл kit.zip, чтобы извлечь его содержимое. 5. В наборе инструментальных средств разработки содержится описание двух схем XSD: упрощенной и полной. Откройте файл READMEFIRST.txt и отыщите в нем ссылку на полную XSD&схему. На момент написания этой книги полная XSD&схема была доступна по адресу: http://xml.amazon.com/schemas3/ dev-heavy.xsd. 434 Часть II Автоматизация Excel 6. Откройте ссылку в обозревателе Internet и сохраните XSD&схему на жестком диске компьютера. Создайте новую рабочую книгу Excel и присоедините к ней XSD&схему, выполнив следующие действия. 1. Выберите в меню Excel (Data XML XML Source). команду Данные XML Источник XML 2. Щелкните на кнопке Карты XML (XML Maps), расположенной в области задач Источник XML (XML Source). 3. В открывшемся диалоговом окне Карты XML (XML Maps) щелкните на кнопке Добавить (Add). 4. Выберите сохраненный ранее файл dev-heavy.xsd и щелкните на кнопке Открыть (Open). 5. Выберите элемент ProductInfo в списке Выберите корень (Please select a root) диалогового окна Несколько корней (Multiple Roots) и щелкните на кноп& ке OK. 6. Щелкните на кнопке OK, чтобы закрыть диалоговое окно Карты XML. В области задач Источник XML появится список полей схемы ProductInfo. Пере& несите нужные поля на рабочий лист, как показано на рис. 15.7. Рис. 15.7. Присоединив XSD&схему к рабочей книге, перетащите требуемые поля из области задач Источник XML (XML Source) на рабочий лист Excel Поддержка XML в профессиональном выпуске Excel 2003 Глава 15 435 Завершив подготовку рабочего листа, откройте редактор Visual Basic, чтобы соз& дать код, извлекающий XML&данные с сайта Amazon.com. Выберите в меню редактора Visual Basic команду Tools References (Сервис Ссылки) и установите флажок, соответствующий библиотеке Microsoft WinHTTP Services. Щелкните на кнопке OK, чтобы закрыть диалоговое окно References (Ссылки). Набор инструментальных средств разработки Amazon.com содержит примеры за& просов в виде строк URL по имени автора, издателю, коду ISBN и т.п. Следующий макрос формирует строку запроса URL и отправляет ее в виде HTTP& запроса серверу Amazon.com. Результат запроса копируется на рабочий лист с помощью метода ImportXML. Ниже приведен код макроса, запрашивающего у Amazon.com все книги Билла Джелена (Bill Jelen), изданные в 2002 году. Sub QuerybyAuthor() ' Запрос к Amazon.com возвращает все книги ' Билла Джелена (Bill Jelen), изданные в 2002 году. Dim ws As Worksheet Dim whr As New WinHttpRequest Dim lobj As ListObject Dim nCount As Integer Dim objSelected As Object Dim sURI As String Set objSelected = Selection Set ws = Worksheets("Sheet1") Set lobj = ws.ListObjects(1) Application.Cursor = xlWait sURI = "http://xml.amazon.com/onca/xml2?t=webservices-20 _ &dev-t=D3VCCO47XZEQFA" _ & "&PowerSearch=author:Bill Jelen and pubdate:2002" _ & "&mode=books&type=heavy&page=1&f=xml" whr.Open "GET", sURI whr.Send ActiveWorkbook.XmlMaps(1).ImportXml whr.ResponseText, _ Overwrite:=True Application.Cursor = xlDefault End Sub Результат запроса помещается в объект Список (List) на рабочем листе Excel. Осо& бенности структуры XML&данных, полученных с Amazon.com, обуславливают на& личие более чем одной строки для книг, написанных несколькими авторами (по одной на каждого автора). Подобный запрос может использоваться для отслежи& вания рейтинга книг и текущей стоимости их подержанных экземпляров (рис. 15.8). 436 Часть II Автоматизация Excel Рис. 15.8. Метод ImportXML используется для копирования результатов запроса на рабочий лист. Обратите внимание, что на экране будут видны только те поля XML&данных, которые бы& ли сопоставлены с рабочим листом путем перетаскивания из области задач Источник XML Извлечение нескольких страниц XML-данных Для извлечения XML&данных, занимающих несколько страниц, необходимо вы& полнить соответствующее число запросов. Номер запрашиваемой страницы ука& зывается в строке URL с помощью параметра page (например, page=2). Чтобы указать на необходимость добавления извлеченных XML&данных к существующе& му объекту Список (List), установите параметр Overwrite метода ImportXML равным False. Следующий макрос запрашивает у Amazon.com две страницы XML&данных, сгенерированных в результате запроса книг, опубликованных из& дательством Sams в 2003 году. Sub QuerybyPublisher() Dim ws As Worksheet Dim whr As New WinHttpRequest Dim lobj As ListObject Dim nCount As Integer Dim objSelected As Object Dim sURI As String Set objSelected = Selection Set ws = Worksheets("Sheet1") Set lobj = ws.ListObjects(1) Поддержка XML в профессиональном выпуске Excel 2003 Глава 15 437 Application.Cursor = xlWait sURI = "http://xml.amazon.com/onca/xml2?t=webservices-20 _ &dev-t=D3VCCO47XZEQFA" _ & "&PowerSearch=publisher:Sams and pubdate:2003" _ & "&mode=books&type=heavy&page=1&f=xml" ' Получить первые 20 строк XML-данных. whr.Open "GET", sURI whr.Send ActiveWorkbook.XmlMaps(1).ImportXml whr.ResponseText, _ Overwrite:=True ' Получить следующие 20 строк XML-данных. sURI = "http://xml.amazon.com/onca/xml2?t=webservices-20 _ &dev-t=D3VCCO47XZEQFA" _ & "&PowerSearch=publisher:Sams and pubdate:2003" _ & "&mode=books&type=heavy&page=2&f=xml" whr.Open "GET", sURI whr.Send ActiveWorkbook.XmlMaps(1).ImportXml whr.ResponseText, _ Overwrite:=False Application.Cursor = xlDefault End Sub Результат выполнения макроса QuerybyPublisher показан на рис. 15.9. Рис. 15.9. Если XML&данные занимают несколько страниц, для их извлечения потребуется выполнить соответствующее число запросов 438 Часть II Автоматизация Excel Следует отметить, что за счет наличия огромного числа полей карта ProductInfo_Map позволяет легко манипулировать отображаемыми результатами запроса без необходимости внесения изменений в код VBA. Например, чтобы получить све& дения о дате выхода книги (ReleaseDate), ее номере в каталоге Amazon.com (ListID) или средней оценке книги читателями (AvgCustomerRating), перета& щите на рабочий лист соответствующие поля из области задач Источник XML (XML Source). Следующий шаг Основная задача XML заключается в обеспечении возможности обмена данными между разнородными приложениями. Программы, входящие в соY став пакета Microsoft Office, обладают встроенной возможностью обмена данY ными между собой. Следующая глава посвящена автоматизации управления текстовым редактором Microsoft Word средствами Excel VBA. Глава 16 Àâòîìàòèçàöèÿ Word Word, Excel, PowerPoint, Outlook и Access используют один и тот же язык программирования VBA, отличаясь между собой только объектной модеY лью (например, рабочая книга Excel представлена объектом Workbook, а документ Word — объектом Document). Каждое из перечисленных приложений может получить доступ к объектной модели другого прилоY жения при условии, что последнее установлено на компьютере. Для доступа к объектной библиоY теке Word из кода Excel VBA на нее необходимо установить ссылку поY средством раннего или позднего связы* вания. Ранее связывание подразумеY вает создание ссылки на объект приY ложения во время компиляции программы, а позднее связывание YYYY во время ее выполнения. В этой главе рассматривается досY туп к объектной модели Word средстY вами Excel VBA. Чтобы познакомитьY ся со структурой объектной модели Word или другого приложения, вхоY дящего в состав пакета Microsoft OfY fice, воспользуйтесь диспетчером объектов редактора Visual Basic соотY ветствующего приложения. Раннее связывание Код, созданный с применением раннего связывания, выполняется быстрее, чем код, созданный с приY менением позднего связывания. 16 Раннее связывание ..................... 439 Позднее связывание................... 442 Работа с объектами .................... 443 Объекты Word.............................. 445 Следующий шаг........................... 460 440 Часть II Автоматизация Excel Раннее связывание предполагает создание ссылки на объектную библиотеку Word на этапе разработки программного кода. Это делает объекты, свойства и методы Word доступными в диспетчере объектов Visual Basic, что, в частности, позволяет отображать динамические подсказки, как показано на рис. 16.1. Рис. 16.1. Раннее связывание позволяет существенно упростить знакомство с синтаксисом объектной модели Word Недостатком раннего связывания является требование обязательного приY сутствия в системе объектной библиотеки, на которую создается ссылка. Именно поэтому макрос, использующий объектную модель Word 2003 и ранY нее связывание, не удастся выполнить на компьютере с Word 2002. Чтобы добавить ссылку на объектную библиотеку Word с помощью редакY тора Visual Basic, выполните следующие действия. 1. Выберите в меню редактора Visual Basic команду Tools References (Сервис Ссылки). 2. В списке Available References (Доступные ссылки) установите флажок рядом со ссылкой на библиотеку Microsoft Word 11.0 Object Library (рис. 16.2). 3. Щелкните на кнопке OK. На заметку Отсутствие объектной библиотеки Word в списке Available References означает, что приложение не установлено на компьютере. Если в списке доступных ссылок содержится объектная библиотека другой версии Word (например, 10.0), значит на компьютере установлена другая версия этого текстового редактора. Добавив ссылку на библиотеку, можно начинать объявлять переменные, используя типы из объектной модели Word. Если в качестве типа переменной указать тип Object, это приведет к использованию позднего связывания. Автоматизация Word Глава 16 441 Рис. 16.2. Выберите требуемую объектную библиотеку в списке Available References Совет В поисках ссылки на тип объекта Excel последовательно просматривает все под& ключенные библиотеки. Если нужная ссылка содержится более чем в одной биб& лиотеке, Excel выберет ссылку, встретившуюся первой. Повлиять на выбор той или иной библиотеки можно путем изменения ее приоритета в списке Available References. Sub WordEarlyBinding() ' Раннее связывание. Dim wdApp As Word.Application Dim wdDoc As Document Set wdApp = New Word.Application Set wdDoc = wdApp.Documents.Open(ThisWorkbook.Path & _ "\Автоматизация Word.doc") wdApp.Visible = True Set wdApp = Nothing Set wdDoc = Nothing End Sub Макрос WordEarlyBinding открывает существующий документ Word. ПеY ременная wdApp используется для обращения к объекту Word.Application (аналог объекту Application в Excel VBA), а переменная wdDoc — для обY ращения к объекту Document. Для создания нового экземпляра Word испольY зуется выражение New Word.Application. После создания экземпляра Word освободите память, занимаемую переY менными wdApp и wdDoc, присвоив каждой из них значение Nothing. 442 Часть II Автоматизация Excel Совет Созданный экземпляр Word не отображается на экране, если в момент выполне& ния макроса в системе отсутствует другая активная копия Word. Чтобы вывести окно Word на экран, присвойте свойству Visible объекта Word.Application значение True. Ошибка компиляции: отсутствие библиотеки Если объектная библиотека Word, на которую была создана ссылка, отсутY ствует в системе, Excel не сможет откомпилировать макрос и отобразит сообY щение об ошибке. В списке Available References (Доступные ссылки) отсутY ствующая библиотека будет отмечена словом ‘‘MISSING’’ (‘‘Отсутствует’’), как показано на рис. 16.3. Рис. 16.3. Excel не сможет откомпилировать макрос, если требуемая объектная библиотека отсутствует в системе Если в системе присутствует другая версия объектной библиотеки Word, можно попытаться откомпилировать макрос, создав ссылку на эту библиотеY ку. Различные версии библиотек Word содержат много одинаковых объектов. Позднее связывание Позднее связывание предполагает создание объектов Word до установки ссылки на соответствующую объектную библиотеку. Это позволяет использоY вать любую версию библиотеки Word, содержащую требуемые объекты, метоY ды и свойства. К тому же, версию библиотеки Word можно определить в коде макроса и создавать экземпляры только содержащихся в ней объектов. Недостаток позднего связывания заключается в полной неосведомленноY сти Excel об обращении к объектной модели Word. Помимо невозможности Автоматизация Word Глава 16 443 отображения динамических подсказок, Excel не позволяет использовать встроенные константы Word и не может проверить корректность создаваемых ссылок. В результате этого все ошибки, связанные с использованием библиоY теки Word, обнаруживаются во время выполнения программного кода. Следующий макрос открывает существующий документ Word и выводит его на экран. Sub WordLateBinding() ' Позднее связывание. Dim wdApp As Object, wdDoc As Object Set wdApp = CreateObject("Word.Application") Set wdDoc = wdApp.Documents.Open(ThisWorkbook.Path & _ "\Автоматизация Word.doc") wdApp.Visible = True Set wdApp = Nothing Set wdDoc = Nothing End Sub Переменная wdApp содержит ссылку на объект приложения (CreateObject ("Word.Application")) и используется при создании переменной wdDoc, ссылающейся на объект библиотеки Word. На заметку Необходимость использования позднего связывания обусловлена выбором типа переменных wdApp и wdDoc (Object). Макрос не может создать требуемые ссыл& ки на объекты библиотеки Word до выполнения функции CreateObject. Работа с объектами В следующих разделах рассматривается создание новых объектов, а также обращение к уже существующим объектам. Ключевое слово New Ключевое слово New можно использовать для создания объекта приложеY ния при раннем связывании (см. макрос WordEarlyBinding выше в этой главе). Аналогичного результата позволяет добиться также функция CreateObject, а вот функция GetObject предназначена для получения ссылки на уже существующий объект. Использовать ключевое слово New при позднем связывании нельзя. Внимание Если окно созданного экземпляра Word не отображается на экране, откройте при& ложение Диспетчер задач Windows (Windows Task Manager) и проверьте наличие в памяти компьютера процесса WINWORD.EXE. Если процесс запущен, выполните следующую строку кода с помощью окна Immediate (Быстрое выполнение) ре& дактора Visual Basic: Word.Application.Visible = True 444 Часть II Автоматизация Excel При наличии в памяти компьютера нескольких экземпляров процесса WINWORD.EXE выполняйте приведенную выше строку до тех пор, пока не доберетесь до нужной копии Word, попутно закрывая окна “лишних” экземпляров. Функция CreateObject Функцию CreateObject можно использовать для создания нового экY земпляра объекта как при позднем (см. макрос WordLateBinding выше в этой главе), так и при раннем связывании. В качестве обязательного параметY ра функции CreateObject необходимо передать имя приложения и тип созY даваемого объекта (например, Word.Application). Функция GetObject Функция GetObject возвращает ссылку на уже существующий экземпляр объекта. Если обнаружить указанный экземпляр объекта не удалось, генериY руется сообщение об ошибке. Первый параметр функции GetObject определяет полный путь к файлу, содержащему требуемый объект. Второй параметр функции GetObject задаY ет класс объекта. Несмотря на то что по отдельности каждый из параметров является необязательным, пропуск первого из них автоматически означает необходимость указания второго. Sub UseGetObject() Dim wdDoc As Object Set wdDoc = GetObject(ThisWorkbook.Path & _ "\Автоматизация Word.doc") wdDoc.Application.Visible = True Set wdDoc = Nothing End Sub Макрос UseGetObject открывает документ внутри существующего экземпY ляра приложения Word и выводит окно последнего на экран. Поскольку переменY ная wdDoc ссылается на объект документа, для вывода окна Word на экран исY пользуется свойство Application объекта Document (wdDoc.Application.Visible = True). На заметку Несмотря на установку значения свойства Visible объекта Application рав& ным True, макрос UseGetObject не делает приложение Word активным. Следующий код пытается вставить диаграмму Excel в конец открытого доY кумента Word, предварительно отключив обработку ошибок. Если на момент выполнения кода ни один документ Word не был открыт, создается новый доY кумент. Sub IsWordOpen() Dim wdApp As Object ActiveChart.ChartArea.Copy Автоматизация Word Глава 16 445 On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 With wdApp.Selection .EndKey Unit:=wdStory .TypeParagraph .PasteSpecial link:=False, DataType:=wdPasteOLEObject, _ Placement:=wdInLine, DisplayAsIcon:=False End With Set wdApp = Nothing End Sub Обработка ошибок отключается с помощью строки On Error Resume Next. Ошибка возникает при попытке связать переменную wdApp с несущеY ствующим объектом. В этом случае переменная wdApp не будет иметь значеY ния, чем можно воспользоваться (If wdApp Is Nothing Then) для открыY тия нового экземпляра Word, создания пустого документа и отображения окна Word на экране. Чтобы открыть новый экземпляр Word, значение первого паY раметра функции GetObject задается как "". Возвратиться к обычному реY жиму обработки ошибок позволяет строка On Error GoTo 0. Объекты Word Получить первичное представление об объектной модели Word поможет средство записи макросов Word. Помня обо всех недостатках средства записи макросов Excel, рассматривавшегося в начале этой книги, обращайте внимаY ние только на использованные в сгенерированном коде объекты, методы и свойства. Внимание Средство записи макросов Word накладывает некоторые ограничения на действия пользователя. В частности, перемещать курсор и выделять объекты разрешается только с помощью клавиатуры. Использование мыши для выполнения указанных операций не предусмотрено. Ниже приведен код, сгенерированный средством записи макросов Word при добавлении нового документа: Documents.Add Template:="Normal", NewTemplate:=False, _ DocumentType:=0 446 Часть II Автоматизация Excel На самом деле, добавить новый документ Word можно с помощью гораздо более простого и эффективного кода: Documents.Add Свойства Template, NewTemplate и DocumentType необязательны и обычно используются для гарантированного задания значений параметров. Чтобы выполнить приведенный выше код в Excel, необходимо создать ссылку на объектную библиотеку Word. В следующих разделах рассматриваY ются основные объекты Word. Для более подробного изучения объектной моY дели Word обратитесь к диспетчеру объектов редактора Visual Basic. Объект Document Объект Word Document является эквивалентом объекта Excel Workbook. Документ Word состоит из символов (свойство Characters объекта Document), слов (свойство Words объекта Document), предложений (свойство Sentences объекта Document), абзацев (свойство Paragraphs объекта Document), разделов (свойство Sections объекта Document), а также верхY них (свойство Headers объекта Section) и нижних колонтитулов (свойство Footers объекта Section). Свойства и методы объекта Document позволяY ют создавать новые документы Word, закрывать существующие документы, осуществлять печать, редактирование и многое другое. Создание документа Word Чтобы создать новый документ внутри существующего экземпляра Word, воспользуйтесь методом Add (для создания нового документа с открытием экY земпляра Word примените функцию CreateObject или GetObject, о чем уже говорилось ранее в этой главе). Sub NewDocument() Dim wdApp As Word.Application On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 Set wdApp = Nothing End Sub Макрос NewDocument создает новый документ Word на основе стандартY ного шаблона. Ниже приведен пример создания нового документа Word на основе шаблона Современная записка (Contemporary Memo). wdApp.Documents.Add Template:="Современная записка.dot" Автоматизация Word Глава 16 447 В качестве значения параметра Template следует указать либо полный путь к файлу шаблона, либо имя файла одного из шаблонов, поставляющихся вместе с Word. Открытие и сохранение документа Word Чтобы открыть существующий документ Word, воспользуйтесь методом Open. Этот метод имеет несколько параметров, таких как ReadOnly и AddtoRecentFiles. В результате выполнения следующего кода документ Автоматизация Word.doc будет открыт в режиме только для чтения и не будет добавлен в список ранее открывавшихся файлов: wdApp.Documents.Open Filename:="C:\Автоматизация Word.doc", _ ReadOnly:=True, AddtoRecentFiles:=False Чтобы сохранить документ под его текущим именем, воспользуйтесь метоY дом Save: wdApp.Documents.Save Чтобы сохранить документ под новым именем (в том числе новый докуY мент), воспользуйтесь методом SaveAs: wdApp.ActiveDocument.SaveAs "C:\Служебная записка.doc " На заметку Метод SaveAs предполагает использование объекта ActiveDocument. Закрытие документа Word Чтобы закрыть определенный документ Word или все открытые докуменY ты, воспользуйтесь методом Close. По умолчанию при попытке закрытия документа с несохраненными изменениями выводится окно сообщения. Чтобы закрыть все открытые документы без сохранения изменений, устаноY вите значение параметра SaveChanges метода Close равным wdDoNotSaveChanges: wdApp.Documents.Close SaveChanges:=wdDoNotSaveChanges Ниже приведен пример закрытия текущего документа: wdApp.ActiveDocument.Close и документа с указанным именем: wdApp.Documents("Автоматизация Word.doc").Close Печать документа Word Чтобы отправить на печать документ Word или его часть, воспользуйтесь методом PrintOut. Ниже приведен пример печати текущего документа со стандартными параметрами печати: wdApp.ActiveDocument.PrintOut 448 Часть II Автоматизация Excel По умолчанию на печать выводится весь документ целиком. Чтобы напечаY тать только определенный диапазон страниц, воспользуйтесь параметрами Range и Pages метода PrintOut: wdApp.ActiveDocument.PrintOut Range:=wdPrintRangeOfPages, Pages:="2" Объект Selection Объект Selection используется для представления выделенной области в документе Word (слова, предложения, места вставки и т.п.). Тип выделенной области хранится в свойстве Type объекта Selection (wdSelectionIP, wdSelectionColumn, wdSelectionShape и т.п.). Методы HomeKey и EndKey Методы HomeKey и EndKey используются для изменения выделенной обY ласти и эквивалентны нажатию клавиш <Home> и <End>, соответственно. Каждый метод принимает 2 параметра: Unit и Extend. Параметр Unit опреY деляет диапазон смещения или расширения выделенной области: начало (метод HomeKey) или конец (метод EndKey) строки текста (wdLine), докуY мента (wdStory), столбца таблицы (wdColumn) или строки таблицы (wdRow). Параметр Extend определяет тип действия: смещение выделенной области (wdMove) или ее расширение (wdExtend). Следующий код перемещает курсор в начало документа: wdApp.Selection.HomeKey Unit:=wdStory, Extend:=wdMove В результате выполнения приведенной ниже строки кода будет выделен фрагмент от текущего места вставки до конца документа: wdApp.Selection.EndKey Unit:=wdStory, Extend:=wdExtend Метод TypeText Метод TypeText используется для вставки текста в документ Word. СпоY соб вставки текста определяется значением различных параметров приложеY ния, таких как параметр Overtype. Sub InsertText() Dim wdApp As Word.Application Dim wdDoc As Document Dim wdSln As Selection On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 Автоматизация Word Глава 16 449 Set wdDoc = wdApp.ActiveDocument Set wdSln = wdApp.Selection wdDoc.Application.Options.Overtype = False With wdSln If .Type = wdSelectionIP Then .TypeText ("Вставка текста в месте текущего _ положения курсора.") ElseIf .Type = wdSelectionNormal Then If wdApp.Options.ReplaceSelection Then .Collapse Direction:=wdCollapseStart End If .TypeText ("Вставка текста перед выделенным блоком.") End If .TypeText vbCrLf End With Set wdApp = Nothing Set wdDoc = Nothing End Sub Объект Range Объект Range используется для представления непрерывной области (диапазона) в документе Word. Диапазон определяется позицией первого и последнего входящего в него символа. Примером диапазона является место вставки, фрагмент текста, а также весь документ Word вместе с непечатаемыY ми символами, такими как символ пробела и символ абзаца. Несмотря на кажущуюся схожесть, объект Range выгодно отличается от объекта Selection. В частности, использование объекта Range позволяет создавать более эффективный код за счет упрощенного способа обращения к требуемым элементам документа Word. Определение диапазона Чтобы определить диапазон, укажите позицию первого и последнего вхоY дящего в него символа: Sub RangeText() Dim wdApp As Word.Application Dim wdDoc As Document Dim wdRng As Word.Range On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 InsertText Set wdDoc = wdApp.ActiveDocument 450 Часть II Автоматизация Excel Set wdRng = wdDoc.Range(0, 22) wdRng.Select Set wdApp = Nothing Set wdDoc = Nothing Set wdRng = Nothing End Sub Результат выполнения макроса RangeText показан на рис. 16.4. Рис. 16.4. Пример выделения фрагмента текста с помо& щью объекта Range Диапазон, представленный объектом Range, включает 22 символа, в том числе символы абзаца. На заметку Выделение диапазона wdRng в макросе RangeText было предпринято для повы& шения наглядности рассматриваемого примера. Работать с диапазоном можно и без его предварительного выделения. Позиция первого символа в документе Word всегда равна нулю. Позиция последнего символа в документе Word равна общему числу символов в доY кументе. Объект Range можно использовать для выделения абзацев. Следующий макрос выделяет третий абзац документа Word, копирует его и помещает на рабочий лист Excel как объект Word (рис. 16.5) и как обычный текст (рис. 16.6). Sub SelectSentence() Dim wdApp As Word.Application Dim wdRng As Word.Range On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 InsertText InsertText InsertText With wdApp.ActiveDocument If .Paragraphs.count >= 3 Then Set wdRng = .Paragraphs(3).Range Автоматизация Word Глава 16 451 wdRng.Copy End If End With ' Вставить скопированный абзац как объект Word. Worksheets("Объекты Word").Select Range("B22").Select ActiveSheet.PasteSpecial ' Вставить скопированный абзац в ячейку B24 как обычный текст. ' Обратите внимание, что перед вставкой абзаца нужно выделить ' соответствующий диапазон рабочего листа Excel. Range("B24").Select ActiveSheet.Paste Set wdApp = Nothing Set wdRng = Nothing End Sub Рис. 16.5. Скопированный абзац помещается на рабочий лист Excel как объект Word Рис. 16.6. Скопированный абзац помещается на ра& бочий лист Excel как обычный текст Стилевое форматирование диапазона Следующий макрос добавляет полужирное начертание к первому слову всех абзацев документа Word. Sub ChangeFormat() Dim wdApp As Word.Application Dim wdRng As Word.Range Dim count As Integer On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 InsertText InsertText InsertText With wdApp.ActiveDocument For count = 1 To .Paragraphs.count Set wdRng = .Paragraphs(count).Range With wdRng .Words(1).Font.Bold = True .Collapse 452 Часть II Автоматизация Excel End With Next count End With Set wdApp = Nothing End Sub Результат выполнения макроса ChangeFormat показан на рис. 16.7. Рис. 16.7. Макрос ChangeFormat добавляет полужир& ное начертание к первому слову всех абзацев доку& мента Word Один из наиболее быстрых способов изменения форматирования абзаца заключается в применении к нему другого стиля. Следующий макрос изменяY ет стиль абзацев документа Word с Обычный на Заголовок 1. Sub ChangeStyle() Dim wdApp As Word.Application Dim wdRng As Word.Range Dim count As Integer On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 InsertText InsertText InsertText With wdApp.ActiveDocument For count = 1 To .Paragraphs.count Set wdRng = .Paragraphs(count).Range With wdRng If .Style = "Обычный" Then .Style = "Заголовок 1" .Collapse End If End With Next count End With Автоматизация Word Глава 16 453 Set wdApp = Nothing Set wdRng = Nothing End Sub Результат выполнения макроса ChangeStyle показан на рис. 16.8. Рис. 16.8. Макрос ChangeStyle изменяет стиль абзацев документа с Обычный на Заголовок 1 Закладки Закладки предназначены для упрощения навигации по документу Word. Объект Bookmarks доступен в виде свойства объектов Document, Selection и Range. На заметку Закладки можно создать непосредственно в программном коде. Закладка, назначенная некоторой позиции в документе Word, имеет IYобразный вид. Чтобы отобразить закладки, выберите в меню Word команду Сервис Параметры (Tools Options) и установите флажок Закладки (Bookmarks), расположенный во вкладке Вид (View) (рис. 16.9). Следующий макрос создает закладки Кому, Копия, От и Тема, а затем поY мещает после каждой из них соответствующий текст. 454 Часть II Автоматизация Excel Рис. 16.9. Чтобы отобразить закладки, установите флажок Закладки Sub UseBookmarks() Dim myArray() Dim wdBkmk As String Dim wdApp As Word.Application Dim wdRng As Word.Range Dim wdSln As Selection myArray = Array("Кому", "Копия", "От", "Тема") On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp .Documents.Add .Visible = True End With End If On Error GoTo 0 Set wdSln = wdApp.Selection With wdSln .TypeText "Кому: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add "Кому", wdRng .TypeText vbCrLf .TypeText "Копия: " Автоматизация Word Глава 16 455 Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add "Копия", wdRng .TypeText vbCrLf .TypeText "От: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add "От", wdRng .TypeText vbCrLf .TypeText "Тема: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add "Тема", wdRng End With Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(0)).Range wdRng.InsertBefore ("Билл Джелен") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(1)).Range wdRng.InsertBefore ("Трейси Сирстад") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(2)).Range wdRng.InsertBefore ("MrExcel") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(3)).Range wdRng.InsertBefore ("Продажи фруктов") Set wdApp = Nothing Set wdRng = Nothing End Sub Результат выполнения макроса UseBookmarks показан на рис. 16.10. Рис. 16.10. Закладки существенно упро& щают перемещение по документу Word Закладки можно использовать не только для вставки текста, но и для вставки других объектов, таких как диаграммы. Sub CreateMemo() Dim myArray() Dim wdBkmk As String Dim wdApp As Word.Application Dim wdRng As Word.Range myArray = Array("Кому", "Копия", "От", "Тема", "Диаграмма") On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", "Word.Application") End If If wdApp.ActiveDocument Is Nothing Then With wdApp 456 Часть II Автоматизация Excel .Documents.Add .Visible = True End With End If On Error GoTo 0 Set wdSln = wdApp.Selection With wdSln .TypeText "Кому: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add .TypeText vbCrLf .TypeText "Копия: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add .TypeText vbCrLf .TypeText "От: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add .TypeText vbCrLf .TypeText "Тема: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add .TypeText vbCrLf .TypeText "Диаграмма: " Set wdRng = wdApp.Selection.Range wdApp.ActiveDocument.Bookmarks.Add End With "Кому", wdRng "Копия", wdRng "От", wdRng "Тема", wdRng "Диаграмма", wdRng Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(0)).Range wdRng.InsertBefore ("Билл Джелен") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(1)).Range wdRng.InsertBefore ("Трейси Сирстад") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(2)).Range wdRng.InsertBefore ("MrExcel") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(3)).Range wdRng.InsertBefore ("Продажи овощей и фруктов") Set wdRng = wdApp.ActiveDocument.Bookmarks(myArray(4)).Range ActiveSheet.ChartObjects("Chart 2").Copy wdRng.Select wdRng.Application.Selection.PasteAndFormat Type:=2 wdApp.Activate Set wdApp = Nothing Set wdRng = Nothing End Sub Результат выполнения макроса CreateMemo показан на рис. 16.11. Автоматизация Word Глава 16 457 Рис. 16.11. Закладки можно использовать для помещения в документ Word диаграммы Практикум Создание отчетов в Word с помощью расширенного фильтра В главе 11, “Анализ данных с помощью расширенного фильтра”, рассматривался макрос, использующий расширенный фильтр для создания отчетов по каждому заказчику и сохраняющий их в виде отдельных рабочих книг. Изменим условие задачи, потребовав сохранения полученных отчетов в виде документов Word. Ни& же приведена последовательность действий, которые нужно выполнить для дос& тижения поставленной цели. 1. Создайте новый документ Word. Добавьте в него закладки Заказчик и Таблица, а также, при необходимости, дополнительный текст, который нужно поместить в отчет. Сохраните документ в виде шаблона с именем SalesReport.dot. 2. Внесите изменения в макрос RunReportForEachCustomer (см. главу 11). По& сле применения расширенного фильтра для генерирования отчета в Excel соз& дайте документ Word на основе шаблона SalesReport.dot. 3. С помощью метода InsertBefore поместите имя заказчика в позицию за& кладки Заказчик в новом документе Word. 4. Поместите отчет в позицию закладки Таблица в новом документе Word. До& бавьте полужирное начертание к первой строке таблицы (строке заголовков). Ниже приведен код макроса RunReportForEachCustomer со всеми необходи& мыми изменениями. Sub RunReportForEachCustomer() Dim IRange As Range Dim ORange As Range Dim CRange As Range Dim WBN As Workbook Dim WSN As Worksheet Dim WSO As Worksheet 458 Часть II Автоматизация Excel Dim wdApp As Word.Application Dim wdDoc As Word.Document Dim wdRng As Word.Range Application.ScreenUpdating = False Set WSO = ActiveSheet ' Определение размера исходного диапазона данных. FinalRow = Cells(65536, 1).End(xlUp).Row NextCol = Cells(1, 255).End(xlToLeft).Column + 2 ' ' ' ' Первый расширенный фильтр - создание списка заказчиков в столбце J. Определение целевого диапазона. Копирование заголовка столбца A в ячейку J1. Range("A1").Copy Destination:=Cells(1, NextCol) Set ORange = Cells(1, NextCol) ' Определение исходного диапазона данных. Set IRange = Range("A1").Resize(FinalRow, NextCol - 2) ' Применение расширенного фильтра для отбора ' уникальных значений из столбца A. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:="", CopyToRange:=ORange, Unique:=True FinalCust = Cells(65536, NextCol).End(xlUp).Row ' Цикл по списку заказчиков. For Each cell In Cells(2, NextCol).Resize(FinalCust - 1, 1) ThisCust = cell.Value ' Определение условия отбора. Cells(1, NextCol + 2).Value = Range("A1").Value Cells(2, NextCol + 2).Value = ThisCust Set CRange = Cells(1, NextCol + 2).Resize(2, 1) ' Определение целевого диапазона данных. ' В целевой диапазон войдут столбцы B (Код), ' C (Розничная цена), D (Продано) и E (Остаток). Cells(1, NextCol + 4).Resize(1, 4).Value = _ Array(Cells(1, 2), Cells(1, 3), Cells(1, 4), Cells(1, 5)) Set ORange = Cells(1, NextCol + 4).Resize(1, 4) ' Второй расширенный фильтр - отбор строк, ' удовлетворяющих заданному условию. IRange.AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=CRange, CopyToRange:=ORange ' Добавить итоговую строку. TotalRow = WSO.Cells(65536, _ ORange.Columns(1).Column).End(xlUp).Row + 1 WSO.Cells(TotalRow, ORange.Columns(1).Column).Value = _ "Всего" ' В англоязычной версии Excel: 'WSO.Cells(TotalRow, _ ORange.Columns(2).Column).FormulaR1C1 = "=SUM(R2C:R[-1]C)" Автоматизация Word Глава 16 459 WSO.Cells(TotalRow, _ ORange.Columns(2).Column).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" ' В англоязычной версии Excel: 'WSO.Cells(TotalRow, _ ORange.Columns(4).Column).FormulaR1C1 = "=SUM(R2C:R[-1]C)" WSO.Cells(TotalRow, _ ORange.Columns(4).Column).FormulaR1C1Local = "=СУММ(R2C:R[-1]C)" ' Создание нового документа Word. On Error Resume Next Set wdApp = GetObject(, "Word.Application") If wdApp Is Nothing Then Set wdApp = GetObject("", _ "Word.Application") Set wdDoc = wdApp.Documents.Add(Template:= _ ThisWorkbook.Path & "\SalesTemplate.dot") wdDoc.Activate ' Создание заголовка отчета. wdDoc.Bookmarks("Заказчик").Range.InsertBefore (ThisCust) ' Копирование отчета с рабочего листа Excel в документ Word. WSO.Cells(1, NextCol + 4).CurrentRegion.Copy Set wdRng = wdApp.ActiveDocument.Bookmarks("Таблица").Range wdRng.Select ' Форматирование таблицы. With wdDoc.Application.Selection .Paste With wdDoc.Application.ActiveDocument.Tables(1) .Rows.Alignment = wdAlignRowCenter With .Rows(1) .HeadingFormat = True .Select wdApp.Selection.Font.Bold = True End With End With .HomeKey Unit:=wdStory, Extend:=Move End With ' Сохранение документа Word с последующим закрытием. wdDoc.SaveAs ThisWorkbook.Path & "\" & ThisCust & ".doc" wdDoc.Close savechanges:=False WSO.Select Set wdApp = Nothing Set wdDoc = Nothing Set wdRng = Nothing ' Очистить область вставки результата ' применения расширенных фильтров. Cells(1, NextCol + 2).Resize(1, 10).EntireColumn.Clear Next cell Application.ScreenUpdating = True 460 Часть II Автоматизация Excel Cells(1, NextCol).EntireColumn.Clear MsgBox FinalCust - 1 & " отчетов были успешно созданы!!" Set wdRng = Nothing End Sub Результат выполнения рис. 16.12. макроса RunReportForEachCustomer показан на Рис. 16.12. Пример создания отчета в Microsoft Word Следующий шаг Следующая глава посвящена многомерным массивам. Один из наиболее эффективных способов ускорения обработки данных на рабочем листе заклюY чается в их считывании в многомерный массив, проведении необходимых выY числений и копировании полученных результатов обратно на рабочий лист. Часть III III Удивительные возможности Visual Basic for Applications 17. Массивы ................................................................................ 463 18. Работа с текстовыми файлами ....................................... 473 19. Использование Microsoft Access .....................................489 20. Создание пользовательских объектов, типов и коллекций ........................................................................ 505 21. Пользовательские формы — профессиональL ный подход.......................................................................... 525 22. Интерфейс прикладного программирования (API) Windows ..................................................................... 547 23. Обработка ошибок .............................................................561 24. Создание пользовательских меню и панелей инструментов...................................................................... 575 25. Надстройки .......................................................................... 593 26. Практикум: создание приложения Excel “с нуля”................................................................................. 603 Глава 17 Ìàññèâû Массив YYYY это переменная специY ального типа, которая может хранить более одного элемента данных. Без использования массива для запомиY нания имени и адреса заказчика поY требуется создать две обычные переY менные. В то же время, для хранения целого списка, состоящего из имен и адресов сотен заказчиков, достаточно одного массива. Объявление массива Чтобы объявить массив, укажите число его элементов в скобках после имени переменной, как показано ниже: Dim myArray(2) Приведенный выше код создает массив myArray, состоящий из трех элементов. ДаYда, именно из трех, потому что по умолчанию первый элемент массива имеет индекс 0: myArray(0) = 10 myArray(1) = 20 myArray(2) = 30 Чтобы назначить первому элеменY ту массива индекс 1, воспользуйтесь выражением Option Base 1. ВыY ражение Option Base размещается в разделе объявлений модуля: Option Base 1 Dim myArray(2) Теперь массив myArray будет соY стоять из двух элементов. При создании массива можно заY дать как верхнюю, так и нижнюю его границу: 17 Объявление массива .................. 463 Заполнение массива .................. 464 Манипулирование элементами массива .................. 466 Еще одно преимущество массивов.........................................467 Динамические массивы ............ 469 Передача массива в качестве параметра..................................... 470 Следующий шаг............................ 471 464 Часть III Удивительные возможности Visual Basic for Applications Dim myArray(1 to 10) Dim BigArray(100 to 200) Узнать верхнюю и нижнюю границу массива можно с помощью функций UBound и LBound, соответственно. Выражение Dim myArray(2) определяет только верхнюю границу массива, в то время как выражение Dim myArray (1 to 10) — как верхнюю (10), так и нижнюю (1) границы. Многомерные массивы Рассмотренные выше массивы являются одномерными массивами, положеY ние элементов которых определяется всего одним числом. Одномерный масY сив можно представить в виде строки данных, единственной значимой коорY динатой которой является номер столбца. Ниже приведен пример обращения ко второму элементу одномерного массива (Option Base 0): myArray(1) Во многих ситуациях для представления данных используются многомерные массивы, положение элементов которых определяется несколькими числами (например, двумерный массив можно сравнить с набором строк, значимыми координатами которого являются номер столбца и номер строки). На заметку Двумерный массив часто называют матрицей, что по своей сути близко к понятию электронной таблицы. Действительно, объект Cells определяет положение эле& мента таблицы, т.е. номер его строки и столбца. Ниже приведен пример создания двумерного массива, состоящего из 10 строк и 20 столбцов: Dim myArray(1 to 10, 1 to 20) Следующий код заполняет элементы первой строки массива myArray: myArray(1, 1) = 10 myArray(1, 2) = 20 Аналогичным образом заполняются элементы второй строки массива: myArray(2, 1) = 20 myArray(2, 2) = 40 Конечно же, существуют и более эффективные способы заполнения масY сива, которые рассматриваются далее в этой главе. Заполнение массива Самый простой способ заполнения массива был продемонстрирован в предыдущем разделе. Существуют гораздо более эффективные методы выполY нения аналогичной операции, один из которых приведен ниже: Option Base 1 Sub ColumnHeaders() Массивы Глава 17 465 Dim myArray As Variant Dim myCount As Integer ' Заполнить массив. myArray = Array("Имя", "Адрес", "Телефон", "Адрес эл. почты") ' Скопировать содержимое массива на рабочий лист. With Worksheets("Заполнение массива") For myCount = 1 To UBound(myArray) .Cells(1, myCount).Value = myArray(myCount) Next myCount End With End Sub Результат выполнения макроса ColumnHeaders показан на рис. 17.1. Обратите внимание, что в приведенном выше коде для создания массива была использована переменная myArray типа Variant, которая может храY нить информацию любого рода. Переменная myArray принимает свойства массива после присвоения ей соответствующего значения: myArray = Array("Имя", "Адрес", "Телефон", "Адрес эл. почты") Ниже приведен пример заполнения массива данными, взятыми с рабочего листа Excel (см. рис. 17.2): Dim myArray As Variant myArray = Worksheets("Sheet1").Range("B2:C17") Рис. 17.1. Пример использования массива для создания строки заголовков Рис. 17.2. Массив можно за& полнить данными, взятыми с рабочего листа Excel Следующий макрос заполняет массив данными, взятыми с рабочего листа Excel через одну строку: Sub EveryOtherRow() Dim myArray(1 To 8, 1 To 2) Dim i As Integer, j As Integer, myCount As Integer ' Заполнить массив данными, взятыми ' с рабочего листа Excel через одну строку. For i = 1 To 8 466 Часть III Удивительные возможности Visual Basic for Applications For j = 1 To 2 myArray(i, j) = _ Worksheets("Через один").Cells(i * 2, j + 1).Value Next j Next i ' Скопировать содержимое массива на рабочий лист. For myCount = LBound(myArray) To UBound(myArray) Worksheets("Через один").Cells(myCount * 2, 4) = _ WorksheetFunction.Sum(myArray(myCount, 1), myArray(myCount, 2)) Next myCount End Sub Результат выполнения макроса EveryOtherRow показан на рис. 17.3. Рис. 17.3. Макрос EveryOtherRow подсчитывает сумму данных, взя& тых с рабочего листа Excel через од& ну строку Манипулирование элементами массива Одно из основных предназначений массива заключается в манипулироваY нии его элементами. Следующий макрос вычисляет наибольший элемент масY сива, заполненного данными, взятыми с рабочего листа Excel. Sub QuickFillMax() Dim myArray As Variant myArray = Worksheets("Через один").Range("B2:C17") MsgBox "Максимальное целое число в диапазоне B2:C17 равно " _ & WorksheetFunction.Max(myArray) End Sub Результат выполнения макроса QuickFillMax показан на рис. 17.4. Зачастую результат манипулирования элементами массива помещается на рабочий лист Excel. Следующий макрос вычисляет среднее значение каждой строки массива и помещает полученный результат на рабочий лист. Массивы Глава 17 467 Рис. 17.4. Макрос QuickFillMax вычисляет наибольшее значение в диапазоне B2:C17 Sub QuickFillAverage() Dim myArray As Variant Dim myCount As Integer myArray = Worksheets("Через один").Range("B2:C17") For myCount = LBound(myArray) To UBound(myArray) Worksheets("Через один").Cells(myCount + 1, 5).Value = _ WorksheetFunction.Average(myArray(myCount, 1), myArray(myCount, 2)) Next myCount End Sub На заметку При помещении данных на рабочий лист Excel в качестве адреса строки использу& ется значение myCount + 1. Это обусловлено тем, что нижняя граница массива (LBound) равна 1 (Option Base 1), а данные на рабочем листе размещаются на& чиная со второй строки. Результат выполнения макроса QuickFillAverage показан на рис. 17.5. Еще одно преимущество массивов Неужели все преимущество массивов сводится к упрощению манипулироY вания данными рабочего листа? Конечно же, нет. Основная выгода, получаеY мая от использования массивов, заключается в существенном повышении ско* рости выполнения программного кода. 468 Часть III Удивительные возможности Visual Basic for Applications Рис. 17.5. Макрос QuickFillAverage вы& числяет среднее значение строк массива и помещает полученные результаты на рабо& чий лист Excel Следующий макрос вычисляет среднее значение столбцов B и C без исY пользования массива. Sub SlowAverage() Dim myCount As Integer, LastRow As Integer LastRow = Worksheets("Через один").Range("A65536").End(xlUp).Row For myCount = 2 To LastRow With Worksheets("Через один") .Cells(myCount, 5).Value = _ WorksheetFunction.Average(.Cells(myCount, 2), .Cells(myCount, 3)) End With Next myCount End Sub Макрос SlowAverage обрабатывает каждую строку в области данных раY бочего листа, вычисляя среднее столбцов B и C и помещая полученный реY зультат в столбец E. Не проще ли работать с данными как с единым целым, обращаясь к ячейкам рабочего листа только для заполнения массива и помеY щения на рабочий лист полученного результата? При заполнении массива рекомендуется использовать именованный диаY пазон ячеек, так как это позволяет создавать более универсальный код. Ниже приведены примеры заполнения массива с помощью обычного и именованного диапазона ячеек. myArray = Range("B2:C17") myArray = Range("myData") Совет Присвоение переменной типа Variant содержимого столбца рабочего листа Ex& cel приведет к созданию двумерного массива, для обращения к элементам кото& рого необходимо указывать номер строки и номер столбца. Массивы Глава 17 469 Чтобы упростить работу с подобным массивом, транспонируйте столбец с помо& щью функции Transpose, превратив его тем самым в строку. Присвоение пере& менной типа Variant содержимого строки рабочего листа Excel приведет к соз& данию одномерного массива, что наглядно демонстрирует следующий код. Sub TransposeArray() Dim myArray As Variant myArray = WorksheetFunction.Transpose(Range("myTran")) ' Узнать значение 5-го элемента массива. MsgBox "5-й элемент массива равен " & myArray(5) End Sub Результат выполнения макроса TransposeArray показан на рис. 17.6. Рис. 17.6. Чтобы создать одномерный массив, преобразуйте столбец в строку с помощью функции Transpose Динамические массивы Очень часто размер массива заранее неизвестен. Теоретически можно созY дать массив очень большого размера ‘‘на все случаи жизни’’, однако это решение приведет к напрасной трате ресурсов и не гарантирует 100%Yго результата. ВыY ходом из подобной ситуации является использование динамического массива. Объявление динамического массива аналогично объявлению обычного массива без указания размера последнего: Dim myArray() 470 Часть III Удивительные возможности Visual Basic for Applications Когда размер динамического массива станет известен, инициализируйте его с помощью выражения ReDim: Sub MySheets() Dim myArray() As String Dim myCount As Integer, NumShts As Integer NumShts = ActiveWorkbook.Worksheets.Count ' Инициализация динамического массива. ReDim myArray(1 To NumShts) For myCount = 1 To NumShts myArray(myCount) = ActiveWorkbook.Sheets(myCount).Name Next myCount Worksheets("Через один").Range("J33").Resize(, _ NumShts).Value = myArray End Sub Повторная инициализация массива с помощью выражения ReDim привоY дит к полному уничтожению его предыдущего содержимого. Чтобы предотY вратить потерю текущих значений элементов массива, воспользуйтесь ключеY вым словом Preserve. Следующий макрос создает массив, состоящий из имен файлов рабочих книг Excel, находящихся в заданной папке. Sub XLFiles() Dim FName As String Dim arNames() As String Dim myCount As Integer FName = Dir(ThisWorkbook.Path & "\*.xls") Do Until FName = "" myCount = myCount + 1 ReDim Preserve arNames(1 To myCount) arNames(myCount) = FName FName = Dir Loop On Error Resume Next Worksheets("Через один").Range("J38").Resize(, _ myCount).Value = arNames End Sub Внимание При работе с большими объемами данных использование ключевого слова Preserve внутри цикла приводит к замедлению выполнения программного кода. Ес& ли это возможно, постарайтесь определить размер динамического массива до вхождения в цикл. Передача массива в качестве параметра Массив можно передавать в качестве параметра, подобно обычной переY менной. Это повышает эффективность и читабельность кода. Следующий макрос передает массив myArray в качестве параметра функции RegionSales, которая подсчитывает сумму элементов массива и возвращает полуY ченный результат. Массивы Глава 17 471 Sub PassAnArray() Dim myArray() As Variant Dim myRegion As String myArray = Range("mySalesData") myRegion = InputBox("Введите регион - Запад, Центр или Восток") MsgBox "Продажи в регионе " & myRegion & " составили " & Format(RegionSales(myArray, _ myRegion), "$#,#00.00 ") End Sub Function RegionSales(ByRef BigArray As Variant, _ sRegion As String) As Long Dim myCount As Integer RegionSales = 0 For myCount = LBound(BigArray) To UBound(BigArray) If BigArray(myCount, 1) = sRegion Then RegionSales = BigArray(myCount, _ 4) * BigArray(myCount, 3) + RegionSales End If Next myCount End Function Результат выполнения макроса PassAnArray показан на рис. 17.7. Рис. 17.7. Массив можно передавать в качестве параметра функции или процедуре Следующий шаг Массив YYYY это переменная специального типа, которая может хранить неY сколько элементов данных. Основное предназначение массива заключается в упрощении обработки данных и повышении скорости выполнения проY граммного кода. Следующая глава посвящена работе с текстовыми файлами. Экспортирование данных в текстовый файл может понадобиться для передачи информации в другую систему или создания HTMLYфайла. Глава 18 Ðàáîòà ñ òåêñòîâûìè ôàéëàìè Прежде чем формат XML окончаY тельно утвердится в качестве станY дартного формата файлов, некоторое время наряду с форматом XML шиY рокое распространение будут иметь форматы CSV и TXT. В этой главе рассматривается имY порт и экспорт текстовых файлов поY средством VBA. Экспорт данных в текстовый файл может понадобиться для передачи информации в другую систему или создания HTMLYфайла. Импорт данных из текстового файла Рассмотрим три сценария имY порта данных из текстовых файлов. Если в файле содержится менее 65 536 записей, его можно импорY тировать с помощью метода Workbooks.OpenText. Если в файле содержится более 65 536 записей, но менее 98 304 записей, его можно имY портировать с помощью двух вызовов метода Workbooks.OpenText. Если же в файле содержится более 98 304 записей, его придется имY портировать путем последовательY ного считывания строк. 18 Импорт данных из текстового файла ..............................................473 Экспорт данных в текстовый файл ............................................... 486 Следующий шаг........................... 487 474 Часть III Удивительные возможности Visual Basic for Applications Импорт текстовых файлов, содержащих менее 65 536 записей Существует два основных формата текстовых файлов. Первый формат предполагает разделение полей каждой записи с помощью некоторого симвоY ла, например, запятой, точки с запятой, знака табуляции и т.п. Второй формат подразумевает наличие фиксированной ширины полей каждой записи. Excel одинаково успешно справляется с файлами обоих форматов с помоY щью метода OpenText. При разработке программного кода рекомендуется записать макрос и использовать уже готовые фрагменты кода. Импорт текстового файла с полями фиксированной ширины На рис. 18.1 показан пример текстового файла с полями фиксированной ширины. Рис. 18.1. При открытии текстового файла с полями фиксированной ши& рины необходимо указать точный размер полей При открытии подобного файла необходимо указать точный размер его полей. Чтобы создать соответствующий программный код, воспользуемся средством записи макросов. Начните запись макроса, выбрав в меню Excel команду Сервис Макрос Начать запись (Tools Macros Record New Macro). Выберите команду Файл Открыть (File Open) и откройте нужный файл с помощью диалогового окна Открытие документа (Open). Работа с текстовыми файлами Глава 18 475 В диалоговом окне Мастер текстов (импорт) — шаг 1 из 3 (Text Import Wizard YYYY Step 1 of 3) установите переключатель Фиксированной ширины (Fixed width) и щелкните на кнопке Далее (Next). На рис. 18.2 показан результат попытки автоматического определения шиY рины полей текстового файла. Рис. 18.2. Результат попытки автоматического определения ширины по& лей текстового файла. Очевидно, что Excel затрудняется определить ширину соприкасающихся полей Excel затрудняется определить ширину соприкасающихся полей, таких как Дата и Заказчик. Добавьте требуемые линии, обозначающие конец поля, в области Образец разбора данных (Data preview) диалогового окна Мастер текстов (импорт) — шаг 2 из 3 (Text Import Wizard YYYY Step 2 of 3). Чтобы добавить линию, щелкниY те в нужной позиции в области Образец разбора данных. Чтобы переместить линию, щелкните на ней и перетащите в требуемое место. Чтобы удалить лиY нию, дважды щелкните на ней. Конечный результат определения ширины поY лей текстового файла показан на рис. 18.3. Обратите внимание на линейку, расположенную над областью Образец разбора данных. Каждое ее деление соответствует одному символу в текстоY вом файле. К примеру, поле Заказчик начинается с 25Yй позиции и имеет длину 11 символов. По умолчанию Excel предполагает, что все поля текстового файла имеют формат Общий (General). Измените формат полей, требующих особой обраY ботки. Для этого щелкните на столбце и выберите требуемый формат с помоY щью области Формат данных столбца (Column data format) диалогового окна Мастер текстов (импорт) — шаг 3 из 3 (Text Import Wizard YYYY Step 3 of 3), как показано на рис. 18.4. 476 Часть III Удивительные возможности Visual Basic for Applications Рис. 18.3. Образец разбора данных после добавления двух линий, обо& значающих конец поля, и перемещения линии, разграничивающей по& ля Товар и Дата Рис. 18.4. Измените тип столбца Дата на МДГ (MDY). Откажитесь от импорта столбцов С-ть и Пр-ль, изменив их тип на Пропустить (Skip). Чтобы определить разделитель целой и дробной части, а также разделитель разрядов, щелкните на кнопке Подробнее (Advanced) Например, чтобы изменить тип столбца Дата, щелкните на нем и установите переключатель Дата (Date) в области Формат данных столбца. Укажите формат даты (например, ‘‘деньYYмесяцYYгод’’ или ‘‘месяцYYденьYYгод’’) с помощью раскрыY вающегося списка, расположенного справа от переключателя Дата. Чтобы отказаться от импорта определенного столбца, щелкните на нем и установите переключатель Пропустить столбец (Do not import column (skip)). Работа с текстовыми файлами Глава 18 477 Пропуск столбцов может пригодиться при импортировании текстового файY ла, содержащего нежелательную для разглашения информацию (например, себестоимость товара и прибыль, получаемая от его продажи). Иногда текY стовый файл с полями фиксированной ширины содержит также символыY разделители. Установите переключатель Пропустить столбец для всех столбY цов, содержащих символыYразделители, как показано на рис. 18.5. Рис. 18.5. Переключатель Пропустить столбец рекомендуется устано& вить для всех столбцов текстового файла с полями фиксированной ши& рины, содержащих символы&разделители Полям, содержащим алфавитные символы, можно назначить тип Общий. Назначьте тип Текст (Text) числовым полям, значения которых необходимо импортировать как текст. Примером таких полей является поле почтового инY декса и поле номера банковского счета (оба поля могут содержать значения с ведущими нулями, например, 01234). Внимание Excel воспринимает формулу, введенную в столбец текстового формата, как обыч& ную строку. Чтобы ввести формулу, формат столбца придется изменить на Общий (General). Ниже приведен код импорта текстового файла с полями фиксированной ширины, сгенерированный средством записи макросов. Workbooks.OpenText Filename:="sales.prn", Origin:=1251, _ StartRow:=1, DataType:=xlFixedWidth, FieldInfo:=Array( _ Array(0, 2), Array(8, 1), Array(17, 3), Array(25, 1), _ Array(36, 1), Array(46, 1), Array(56, 9), Array(61, 9)), _ TrailingMinusNumbers:=True 478 Часть III Удивительные возможности Visual Basic for Applications Значение параметра FieldInfo представляет массив двухэлементных массивов, определяющих позицию первого символа поля (с отсчетом от нуля) и его тип. Тип поля представлен числом, соответствующим одной из констант Excel xlColumnDataType (табл. 18.1). Например, массив Array(0, 1) определяY ет поле общего формата, отстающее от начала строки на 0 символов (поле Регион), массив Array(8, 1) — поле общего формата, отстающее от начала строки на 8 символов (поле Товар), а массив Array(17, 3) — поле даты в формате ‘‘месяцYYденьYYгод’’, отстающее от начала строки на 17 символов (поле Дата). Таблица 18.1. Значения констант xlColumnDataType Значение Константа Тип данных 1 xlGeneralFormat Общий 2 xlTextFormat Текстовый 3 xlMDYFormat Дата в формате ‘‘месяцYYденьYYгод’’ 4 xlDMYFormat Дата в формате ‘‘деньYYмесяцYYгод’’ 5 xlYMDFormat Дата в формате ‘‘годYYмесяцYYдень’’ 6 xlMYDFormat Дата в формате ‘‘месяцYYгодYYдень ’’ 7 xlDYMFormat Дата в формате ‘‘деньYYгодYYмесяц’’ 8 xlYDMFormat Дата в формате ‘‘годYYденьYYмесяц’’ 9 xlSkipColumn Пропуск столбца 10 xlEMDFormat Дата в тайском летоисчислении Ввиду относительной сложности кодирования параметра FieldInfo реY комендуется записать макрос и скопировать автоматически сгенерированный фрагмент кода. Внимание Впервые параметр TrailingMinusNumbers был представлен в Excel 2002. Не ис& пользуйте его в макросах, которые могут выполняться в более ранних версиях Ex& cel, например, Excel 97 или Excel 2000, так как это приведет к возникновению ошибки компиляции. Отсутствие параметра TrailingMinusNumbers не скажется на результате выполнения макроса в новых версиях Excel. Импорт текстового файла с символамиLразделителями На рис. 18.6 показан текстовый файл с разделителямиYзапятыми. При открытии подобного файла в Excel укажите используемый символY разделитель и, при необходимости, способ обработки полей. В рассматриваеY Работа с текстовыми файлами Глава 18 479 мом примере значение третьего поля должно быть интерпретировано как дата в формате ‘‘месяцYYденьYYгод’’. Рис. 18.6. При открытии текстового файла с символами&разделителями укажите используемый символ&разделитель (в данном случае это запя& тая) и, при необходимости, способ обработки полей Внимание При открытии текстового файла с расширением .csv, содержащего разделители& запятые, средство записи макросов Excel создаст код, вызывающий метод Workbooks.Open. Чтобы иметь возможность указать способ обработки полей, изме& ните расширение файла на .txt. Начните запись макроса, выбрав в меню Excel команду Сервис Макрос Начать запись (Tools Macros Record New Macro). Выберите команду Файл Открыть (File Open) и откройте нужный файл с помощью диалогоY вого окна Открытие документа (Open). В диалоговом окне Мастер текстов (импорт) — шаг 1 из 3 (Text Import Wizard YYYY Step 1 of 3) установите переключатель С разделителями (Delimited) и щелкните на кнопке Далее (Next). Первоначальный результат автоматического разбора данных, показанный в области Образец разбора данных (Data preview) диалогового окна Мастер текстов (импорт) — шаг 2 из 3 (Text Import Wizard YYYY Step 2 of 3), будет выY глядеть крайне непривлекательно. Это вызвано тем, что Excel по умолчанию использует в качестве символаYразделителя знак табуляции (рис. 18.7). 480 Часть III Удивительные возможности Visual Basic for Applications Рис. 18.7. Результат первоначального разбора данных выглядит как чья& то неудавшаяся шутка. А все из&за того, что Excel по умолчанию исполь& зует в качестве символа&разделителя знак табуляции Сбросьте флажок Знак табуляции (Tab) и установите флажок, соответствуюY щий требуемому символуYразделителю (в данном случае это флажок Запятая (Comma)). Результат повторного разбора данных показан на рис. 18.8. Рис. 18.8. Результат повторного разбора данных после изменения ис& пользуемого символа&разделителя не вызывает никаких нареканий. В целом, разбор текстового файла с символами&разделителями требует выполнения меньшего количества действий, чем разбор текстового файла с полями фиксированной ширины Задайте формат полей, требующих особой обработки, с помощью диалогоY вого окна Мастер текстов (импорт) — шаг 3 из 3 (Text Import Wizard YYYY Step 3 Работа с текстовыми файлами Глава 18 481 of 3). Ниже приведен код импорта текстового файла с символамиY разделителями, сгенерированный средством записи макросов. Workbooks.OpenText Filename:="sales.csv.txt", Origin:=1251, _ StartRow:=1, DataType:=xlDelimited, TextQualifier:= _ xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=False, _ Semicolon:=False, Comma:=True, Space:=False, Other:=False, _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 3), _ Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1), _ Array(8, 1)), TrailingMinusNumbers:=True Несмотря на то что этот код длиннее кода импорта текстового файла с поY лями фиксированной ширины, он менее сложен для восприятия. Параметр FieldInfo представляет массив двухэлементных массивов, определяющих порядковый номер поля (с отсчетом от единицы) и его тип (см. табл. 18.1). Например, массив Array(2, 1) определяет второе поле как поле общего формата, а массив Array(3, 3) — третье поле как поле даты в формате ‘‘месяцYYденьYYгод’’. Длина кода обусловлена явным заданием значений всех параметров, определяющих символыYразделители. Поскольку по умолчанию значение этих параметров равно False, код можно оптимизировать, как поY казано ниже: Workbooks.OpenText Filename:="sales.csv.txt", Origin:=1251, _ StartRow:=1, DataType:=xlDelimited, Comma:=True, _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 3), _ Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1), Array(8, 1)) Чтобы сделать код более удобочитаемым, числовые значения, определяюY щие тип поля, можно заменить соответствующими константами: Workbooks.OpenText Filename:="sales.csv.txt", Origin:=1251, _ StartRow:=1, DataType:=xlDelimited, Comma:=True, _ FieldInfo:=Array(Array(1, xlGeneralFormat), Array(2, _ xlGeneralFormat), Array(3, xlMDYFormat), Array(4, _ xlGeneralFormat), Array(5, xlGeneralFormat), Array(6, _ xlGeneralFormat), Array(7, xlGeneralFormat), Array(8, _ xlGeneralFormat)) Внимание Значения параметров, определяющих символы&разделители, остаются действи& тельными на протяжении всего сеанса работы с Excel. При вставке текстовых дан& ных, содержащихся в буфере обмена, Excel осуществит их разбор в соответствии со значениями параметров, заданными во время последнего вызова метода OpenText. К примеру, название фирмы заказчика “ABC, Inc.” будет разбито на две части (“ABC” и “Inc.”) и помещено в два соседних столбца, если в качестве символа&разделителя использовалась запятая. Excel позволяет использовать любой символYразделитель, а не только симY вол табуляции, запятую, точку с запятой и пробел. Чтобы указать на необхоY димость использования в качестве символаYразделителя знака перенаправлеY ния (|), установите значение параметра Other метода OpenText равным True, а значение параметра OtherChar равным "|", как показано далее: 482 Часть III Удивительные возможности Visual Basic for Applications Workbooks.OpenText Filename:="sales.csv.txt", Origin:=1251, _ StartRow:=1, DataType:=xlDelimited, Other:=True, OtherChar:="|" _ FieldInfo:=Array(Array(1, 1), Array(2, 1), Array(3, 3), _ Array(4, 1), Array(5, 1), Array(6, 1), Array(7, 1), Array(8, 1)) Импорт текстовых файлов, содержащих более 65 536 записей Импорт файла, содержащего более 65 536 записей, с помощью мастера текY стов завершится выдачей сообщения об ошибке Файл загружен не полностью (File not loaded completely) и загрузкой первых 65 536 строк. Импорт того же файла с помощью метода Workbooks.OpenText заверY шится загрузкой первых 65 536 строк без уведомления о внештатной ситуации. Проверьте содержимое ячейки A65536 после импорта файла. Если эта ячейка не пуста, вероятно, файл был загружен некорректно. Импорт текстового файла, содержащего не более 98 304 записей На самом деле, Excel позволяет импортировать текстовый файл, содержаY щий более 65 536 записей. В диалоговом окне Мастер текстов (импорт) — шаг 1 из 3 (Text Import Wizard YYYY Step 1 of 3) есть счетчик Начать импорт со строки (Start Import At Row), являющийся аналогом параметра StartRow меY тода OpenText. Казалось бы, достаточно присвоить параметру StartRow значение 65 537 и продолжить импорт файла. Как бы не так! Попытка присвоY ить параметру StartRow значение, превышающее 32 767, завершится ошибY кой времени выполнения 1004. Дело в том, что в ранних версиях Excel для хранения значения параметра StartRow использовалась переменная типа Integer, максимально допустимое значение которой составляло 32 767. Складывается впечатление, что Microsoft не проводила генеральную ревизию кода импорта текстовых файлов со времен, когда рабочий лист Excel был споY собен вмещать чуть более 16 000 строк! Рассмотрим задачу импорта текстового файла, содержащего сведения о складских запасах 35 магазинов розничной торговли. Известно, что перечень наименований товаров в каждом из магазинов не превышает 2000 позиций. Следовательно, максимальное число записей импортируемого файла составY ляет 70 000. Ниже приведен код импорта текстового файла, содержащего не более 98 304 записей. Sub ImportFile() Dim WBO As Workbook Dim WBC As Workbook Set WBO = ActiveWorkbook ChDir ThisWorkbook.Path ThisFile = "inventory.txt" Workbooks.OpenText Filename:=ThisFile Set WBC = ActiveWorkbook WBO.Worksheets("Данные").Cells.Clear Cells.Copy WBO.Worksheets("Данные").Range("A1") Работа с текстовыми файлами Глава 18 483 WBC.Close False If WBO.Worksheets("Данные").Cells(65536, 1).Value <> "" Then ' Файл содержит более 65536 записей. ' Импортировать заново, начиная со строки 32767. Workbooks.OpenText Filename:=ThisFile, StartRow:=32767 Set WBC = ActiveWorkbook ' Некоторые строки (а именно строки 32767-65536) ' будут скопированы повторно. ' Первые 32770 строк нужно удалить. RowsToSkip = Application.Rows.Count + 1 - 32767 Cells(1, 1).Resize(RowsToSkip, 1).EntireRow.Delete FinalRow = Cells(65536, 1).End(xlUp).Row Cells(1, 1).Resize(FinalRow, 1).Copy _ WBO.Worksheets("Данные").Range("AA1") WBC.Close False End If End Sub Импорт текстового файла построчно Изменим условие задачи, увеличив число розничных магазинов до 50. При этом максимальный размер импортируемого файла возрастет до 100 000 записей и его придется загружать построчно. Импорт текстового файла, содержащего менее 65 536 записей, построчно Откройте файл для чтения с помощью выражения Input As #1. Чтобы считать строку файла в переменную, воспользуйтесь выражением Line Input #1, имя_переменной. Следующий макрос открывает файл inventory.txt, считывает первые 10 строк, помещая их содержимое в ячейки раY бочего листа, после чего закрывает файл. Sub Import10() ChDir ThisWorkbook.Path ThisFile = "inventory.txt" Open ThisFile For Input As #1 For i = 1 To 10 Line Input #1, Data Cells(i, 1).Value = Data Next i Close #1 End Sub Чтобы считать все содержимое файла, воспользуйтесь циклом Do...While и функцией EOF. Значение выражения EOF(1) позволяет опреY делить, был ли достигнут конец файла с номером #1. Следующий макрос поY строчно импортирует содержимое файла inventory.txt (чтобы обеспечить корректное выполнение макроса ImportAll, файл inventory.txt не долY жен содержать более 65 536 записей). Sub ImportAll() ThisFile = "inventory.txt" Open ThisFile For Input As #1 484 Часть III Удивительные возможности Visual Basic for Applications Ctr = 0 Do Line Input #1, Data Ctr = Ctr + 1 Worksheets("Данные ").Cells(Ctr, 1).Value = Data Loop While EOF(1) = False Close #1 Worksheets("Данные ").Select End Sub Результат импорта текстового файла описанным выше способом показан на рис. 18.9. Рис. 18.9. Построчное считывание текстового файла требует последующей обработки данных на рабочем листе Очевидным недостатком такого подхода является помещение содержимого каждой строки файла в ячейку столбца A. Чтобы провести разбор импортированных данных, воспользуйтесь метоY дом TextToColumns. Параметры метода TextToColumns практически иденY тичны параметрам метода OpenText. Cells(1, 1).Resize(Ctr, 1).TextToColumns Destination:= _ Range("A1"), DataType:=xlDelimited, comma:=True, FieldInfo:= _ Array(Array(1, xlGeneralFormat), Array(2, xlGeneralFormat), _ Array(3, xlGeneralFormat), Array(4, xlMDYFormat)) Работа с текстовыми файлами Глава 18 485 Вместо жесткой привязки к конкретному номеру файла рекомендуется исY пользовать функцию FreeFile. Функция FreeFile возвращает целое число, представляющее номер файла, доступный для использования в выражении Open. Ниже приведен полный код макроса, считывающего текстовый файл с менее чем 65 536 записями, построчно. Sub ImportAllAndParse() ThisFile = "inventory.txt" FileNumber = FreeFile Open ThisFile For Input As #FileNumber Ctr = 0 Do Line Input #FileNumber, Data Ctr = Ctr + 1 Worksheets("Данные ").Cells(Ctr, 1).Value = Data Loop While EOF(FileNumber) = False Close #FileNumber Worksheets("Данные ").Select ' Провести разбор данных . Cells(1, 1).Resize(Ctr, 1).TextToColumns Destination:= _ Range("A1"), DataType:=xlDelimited, comma:=True, FieldInfo:= _ Array(Array(1, xlGeneralFormat), Array(2, xlGeneralFormat), _ Array(3, xlGeneralFormat), Array(4, xlMDYFormat)) End Sub Импорт текстового файла, содержащего более 65 536 записей, построчно Для построчного импорта текстового файла, содержащего более 65 536 записей, можно использовать уже знакомое выражение Line Input. Считаем часть файла в ячейки A1:A65534, продолжив считывание оставшихY ся строк, начиная с ячейки AA2 (первая строка отводится для размещения заY головков столбцов). Если и этого окажется недостаточно, продолжим считыY вание, начиная с ячейки BA2, CA2 и т.д. Наличие двух пустых строк в конце каждого столбца с данными обеспечивает корректное определение номера поY следней строки с помощью выражения Range("A65536").End(xlUp).Row. Sub ReadLargeFile() ThisFile = "inventory.txt" FileNumber = FreeFile Open ThisFile For Input As #FileNumber NextRow = 1 NextCol = 1 ' Добавлено для увеличения скорости выполнения макроса . Application.ScreenUpdating = False Application.StatusBar = "Считывание данных из текстового файла" Do While Not EOF(FileNumber) Line Input #FileNumber, Data Cells(NextRow, NextCol).Value = Data NextRow = NextRow + 1 If NextRow = 65535 Then ' Провести разбор данных . Application.StatusBar = "Разбор данных " Range(Cells(1, NextCol), Cells(65536, _ NextCol)).TextToColumns Destination:=Cells(1, NextCol), _ 486 Часть III Удивительные возможности Visual Basic for Applications DataType:=xlDelimited, comma:=True, FieldInfo:=Array(Array(1, _ xlGeneralFormat), Array(2, xlGeneralFormat), Array(3, _ xlGeneralFormat), Array(4, xlMDYFormat)) ' Скопировать заголовки столбцов . If NextCol > 1 Then Range("A1:D1").Copy Destination:=Cells(1, NextCol) End If ' Начать считывание следующего набора данных . NextCol = NextCol + 26 NextRow = 2 Application.StatusBar = "Считывание следующего _ набора данных " End If Loop Close #FileNumber ' Разбор последнего набора данных . FinalRow = NextRow - 1 If FinalRow = 1 Then ' Обработка файла, содержащего ровно 65534 строки . NextCol = NextCol - 26 Else Application.StatusBar = "Разбор последнего набора данных " Range(Cells(2, NextCol), Cells(FinalRow, _ NextCol)).TextToColumns Destination:=Cells(2, NextCol), _ DataType:=xlDelimited, comma:=True, FieldInfo:=Array(Array(1, _ xlGeneralFormat), Array(2, xlGeneralFormat), Array(3, _ xlGeneralFormat), Array(4, xlMDYFormat)) If NextCol > 1 Then Range("A1:D1").Copy Destination:=Cells(1, NextCol) End If End If Cells(1, NextCol).Select DataSets = (NextCol - 1) / 26 + 1 Application.StatusBar = False Application.ScreenUpdating = True End Sub Число наборов данных, созданных в результате выполнения макроса ReadLargeFile, хранится в переменной DataSet. Описанный выше метод позволяет разместить на рабочем листе более 655 000 строк, импортированных из текстового файла. Чтобы применить фильтр или создать отчет на основе нескольких наборов данных, в код макросов, рассмотY ренных в предыдущих главах, потребуется внести соответствующие изменения. Например, можно создать по одной сводной таблице для каждого набора данных, подытожив полученные результаты с помощью еще одной сводной таблицы. Экспорт данных в текстовый файл Чтобы экспортировать данные в текстовый файл, откройте его для записи с помощью выражения Output As #1. Сохраните все требуемые строки в файле с помощью выражения Print #1. Работа с текстовыми файлами Глава 18 487 Прежде чем открыть файл для записи, убедитесь, что на диске не существуY ет копии этого файла. Для этого попытайтесь удалить файл с помощью выраY жения Kill. Чтобы проигнорировать сообщение об ошибке, которое будет сгенерировано, если файл с заданным именем не существует, воспользуйтесь выражением On Error Resume Next. Следующий макрос экспортирует данные рабочего листа в текстовый файл Results.txt. Sub WriteFile() ThisFile = ThisWorkbook.Path & Application.PathSeparator & _ "Results.txt" ' Удалить копию файла. On Error Resume Next Kill (ThisFile) On Error GoTo 0 ' Открыть файл. Open ThisFile For Output As #1 FinalRow = Range("A65536").End(xlUp).Row ' Экспортировать в файл данные рабочего листа. For j = 1 To FinalRow Print #1, Cells(j, 1).Value Next j Close #1 MsgBox "Файл " & ThisFile & " успешно сохранен ." End Sub Аналогичный способ экспорта данных в текстовый файл применялся в главе 14, ‘‘Взаимодействие с Internet’’, при создании WebYстраниц. Следующий шаг Скорость импорта и экспорта текстовых файлов оставляет желать лучшего. В следующей главе рассматривается использование Microsoft Access для обесY печения быстрого доступа к данным, предусматривающего индексацию и многопользовательский режим. Глава 19 Èñïîëüçîâàíèå Microsoft Access В предыдущей главе был предлоY жен метод, позволяющий хранить на одном рабочем листе Excel свыше 650 000 строк данных. Тем не менее, на некотором этапе становится очеY видно, что для хранения больших объемов данных следует применять специализированный инструмент, такой как Microsoft Access. ОсновY ным форматом файлов Microsoft AcY cess является формат MDB (MulY tidimensional Database — многомерY ная база данных). Использование MDBYфайлов моY жет быть оправдано необходимостью обеспечения совместного доступа к данным. Предоставление общего досY тупа к рабочей книге связано с множеY ством ограничений. В частности, досY туп к следующим функциям возможен только при запрете совместного досY тупа к книге: автоматическая вставка проY межуточных итогов; создание сводных таблиц; группировка и структурироваY ние данных; создание, изменение и проY смотр сценариев; защита листов и книг, а также снятие защиты; добавление и изменение усY ловного форматирования; вставка и изменение рисунков и других объектов; 19 ADO и DAO .................................... 490 Объекты ADO ............................... 492 Добавление записи в таблицу Access ............................. 493 Извлечение записей из таблицы Access ............................ 494 Обновление записей таблицы Access ............................................. 496 Удаление записей таблицы Access ............................................. 499 Создание итоговых запросов ........................................ 499 Несколько полезных макросов ....................................... 500 Следующий шаг........................... 503 490 Часть III Удивительные возможности Visual Basic for Applications создание и изменение диаграмм или отчетов сводных диаграмм; удаление рабочих листов. Применение Excel в качестве пользовательского интерфейса, а MDBY файла YYYY в качестве базы данных позволяет добиться оптимального использоY вания возможностей обоих программ. На заметку Формат файлов MDB является официальным форматом файлов как Microsoft Ac& cess, так и Microsoft Visual Basic for Applications. Это позволяет разрабатывать MDB&ориентированные макросы, предназначенные для выполнения на компью& терах без установленной копии Microsoft Access. Тем не менее, создание таблиц и запросов Access возможно только с помощью пользовательского интерфейса этой программы. ADO и DAO На протяжении нескольких лет для доступа к данным, хранящимся во внешних источниках, Microsoft рекомендовала использовать объекты DAO (Data Access Objects — объекты доступа к данным). Начиная с Excel 2000 и VBA 6 Microsoft смещает акцент на объекты ADO (ActiveX Data Objects — объекты данных ActiveX), при этом продолжая обеспечивать поддержку DAO. Обе парадигмы доступа к данным весьма похожи и имеют незначительно отY личающийся синтаксис. Во всех примерах этой главы доступ к данным будет осуществляться с помощью объектов ADO. Более подробно отличия между ADO и DAO рассматриваются в статье базы знаний Microsoft, которую вы найдете по адресу: http://support.microsoft.com/default.aspx?scid=KB;EN-US;q225048& Прежде чем перейти к созданию макросов, использующих ADO, выберите в меню редактора Visual Basic команду Tools References (Сервис Ссылки) и установите флажок, соответствующий библиотеке Microsoft ActiveX Data ObY jects, как показано на рис. 19.1. Щелкните на кнопке OK, чтобы закрыть диаY логовое окно References (Ссылки). Практикум Совместный доступ к MDB-файлу Рассмотрим следующую задачу. Предположим, что пользователь А и пользова& тель Б ответственны за закупку товаров для сети розничных магазинов. Каждое ут& ро они импортируют данные из журнала кассовых операций, чтобы получить ин& формацию о вчерашних продажах и, при необходимости, перераспределить ос& таток товаров между магазинами. Необходимо сделать так, чтобы пользователь А мог видеть перемещения товаров, инициированные пользователем Б, и наоборот. Использование Microsoft Access Глава 19 491 Рис. 19.1. Для доступа к содержимому MDB&файла с по& мощью объектов ADO используется библиотека Micro& soft ActiveX Data Objects На компьютерах обоих пользователей установлена копия Excel с поддержкой VBA. Импортированные данные обрабатываются макросами, в результате выполнения которых создаются сводные таблицы, облегчающие принятие решений о необхо& димости закупки товаров. Попытка хранения данных о перемещениях товаров в рабочей книге Excel обрече& на на провал. Монопольный режим доступа не позволяет изменять файл рабочей книги нескольким пользователям одновременно. С другой стороны, режим со& вместного доступа исключает возможность создания сводных таблиц. Следует отметить, что на компьютерах обоих пользователей нет установленной копии Microsoft Access. Решение поставленной задачи приведено ниже. 1. С помощью копии Microsoft Access, установленной на другом компьютере, создайте базу данных Transfers.mdb и добавьте в нее таблицу tblTransfer, как показано на рис. 19.2. 2. Разместите файл Transfers.mdb на сетевом диске, доступном с компьютеров обоих пользователей. 3. Подключите библиотеку Microsoft ActiveX Data Objects на компьютерах обоих пользователей. 4. Поместите путь к файлу Transfers.mdb в рабочие книги пользователей А и Б, присвоив соответствующей ячейке имя TPath. 5. Для работы с данными, хранящимися в таблице tblTransfer, используйте код, приводящийся в следующих разделах этой главы. Access поддерживает общий доступ к данным. Это означает, что пользователь А и пользователь Б могут просматривать или изменять таблицу tblTransfer одно& временно. Единственным условием для возникновения конфликтной ситуации яв& 492 Часть III Удивительные возможности Visual Basic for Applications ляется попытка совместного изменения пользователями А и Б одной и той же за& писи таблицы. Рис. 19.2. Таблица tblTransfer предназначена для хранения информации о перемещени& ях товаров и будет доступна для изменения несколькими пользователями одновременно Объекты ADO Двумя основными объектами ADO являются объект соединения Connection и объект набора данных Recordset. Объект соединения определяет путь к базе данных и ее тип. База данных Microsoft Access основана на ядре Microsoft Jet. Объект набора данных создается на основе существующего объекта соедиY нения для представления таблицы, некоторого ее подмножества или предоY пределенного запроса базы данных Access. Помимо объекта соединения, при создании объекта набора данных указываются такие параметры, как CursorType, CursorLocation, LockType и Options. Для обеспечения одноY временного доступа к базе данных двух пользователей рекомендуется примеY нять динамический курсор и оптимистическую блокировку. При работе с большими объемами данных установите значение параметра CursorLocation равным adUseServer, что позволит обрабатывать записи в оперативY ной памяти сервера. В противном случае установите значение параметра CursorLocation равным adUseClient. При создании объекта набора данных все записи будут скопированы в оперативную память компьютера пользоватеY ля, что способно существенно ускорить их обработку. Использование Microsoft Access Глава 19 493 Чтобы скопировать записи из таблицы Access на рабочий лист Excel, восY пользуйтесь методом Excel VBA CopyFromRecordset. Чтобы добавить новую запись в таблицу Access, воспользуйтесь методом AddNew объекта Recordset. Укажите значение каждого поля таблицы и выY зовите метод Update объекта Recordset для внесения изменений в базу данных. Чтобы удалить запись из таблицы Access, отправьте запрос на удаление заY писи, удовлетворяющий заданному критерию. Кроме того, средства VBA позволяют проверить существование таблицы или ее поля, а также добавить новые поля к существующей таблице. Добавление записи в таблицу Access Пользователи А и Б вводят информацию о перемещениях товаров в форму. Фактическое добавление записи в таблицу Access возложено на макрос AddTransfer. При добавлении записи в таблицу Access макрос AddTransfer выполняет следующую последовательность действий. 1. Создание объекта соединения. 2. Создание объекта набора данных для обращения к таблице tblTransfer. 3. Вызов метода AddNew для добавления новой записи в таблицу tblTransfer. 4. Установка значения каждого поля новой записи таблицы tblTransfer. 5. Вызов метода Update для внесения изменений в базу данных. 6. Закрытие объектов набора данных и соединения. Ниже приведен код метода AddTransfer. Sub AddTransfer(Style As Variant, FromStore As Variant, _ ToStore As Variant, Qty As Integer) Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset MyConn = ThisWorkbook.Path & Application.PathSeparator & _ "transfers.mdb" ' Открыть соединение. Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With ' Создать объект набора данных. Set rst = New ADODB.Recordset rst.CursorLocation = adUseServer 494 Часть III Удивительные возможности Visual Basic for Applications ' Открыть набор данных. rst.Open Source:="tblTransfer", _ ActiveConnection:=cnn, _ CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic, _ Options:=adCmdTable ' Добавить новую запись. rst.AddNew ' Определить значения полей новой записи. Первые 4 поля ' задаются пользователем с помощью формы. В поле "Дата" ' вносится текущее значение даты. rst("Товар") = Style rst("ИзМагазина") = FromStore rst("ВМагазин") = ToStore rst("Количество") = Qty rst("Дата") = Date rst("Отправлен") = False rst("Получен") = False ' Внести изменения в базу данных. rst.Update ' Закрыть объекты набора данных и соединения. rst.Close cnn.Close End Sub Извлечение записей из таблицы Access Чтобы извлечь записи из таблицы Access, определите набор данных, указав в качестве источника строку SQLYзапроса, задающую требуемый критерий отбора. Скопировать записи из таблицы Access на рабочий лист Excel поможет меY тод Excel VBA CopyFromRecordset. Следующий макрос извлекает из таблицы tblTransfer все записи, в коY торых значение поля Отправлен равно False, и помещает их на рабочий лист. Строка frmTransConf.Show выводит на экран пользовательскую форY му, которая применяется для обновления записей и рассматривается в слеY дующем разделе. Sub GetUnsentTransfers() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Dim WSOrig As Worksheet Dim WSTemp As Worksheet Dim sSQL As String Dim FinalRow As Long Set WSOrig = ActiveSheet ' Создать SQL-запрос для извлечения записей, ' соответствующих неотправленным товарам. sSQL = "SELECT Идентификатор, Товар, ИзМагазина, ВМагазин, _ Использование Microsoft Access Количество, Дата FROM tblTransfer" sSQL = sSQL & " WHERE Отправлен=FALSE" ' Задать путь к файлу Transfers.mdb. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With Set rst = New ADODB.Recordset rst.CursorLocation = adUseServer rst.Open Source:=sSQL, ActiveConnection:=cnn, _ CursorType:=AdForwardOnly, LockType:=adLockOptimistic, _ Options:=adCmdText ' Создать новый рабочий лист. Set WSTemp = Worksheets.Add WSTemp.Select Range("A1:F1").EntireColumn.Clear ' Создать строку заголовков. Range("A1:F1").Value = Array("Идентификатор", "Товар", _ "ИзМагазина", "ВМагазин", "Количество", "Дата") ' Скопировать записи из набора данных на рабочий лист. Range("A2").CopyFromRecordset rst ' Закрыть набор данных и соединение. rst.Close cnn.Close ' Отформатировать отчет. FinalRow = Range("A65536").End(xlUp).Row ' Удалить временный рабочий лист, если в результате ' запроса было возвращено пустое множество записей. If FinalRow = 1 Then Application.DisplayAlerts = False WSTemp.Delete Application.DisplayAlerts = True WSOrig.Activate MsgBox "Неподтвержденных перемещений товаров нет" Exit Sub End If ' Задать формат ячеек столбца F как М.Д.ГГ. Range("F2:F" & FinalRow).NumberFormat = "m/d/y" ' Отобразить форму. frmTransConf.Show ' Удалить временный рабочий лист. Application.DisplayAlerts = False WSTemp.Delete Глава 19 495 496 Часть III Удивительные возможности Visual Basic for Applications Application.DisplayAlerts = True End Sub Метод CopyFromRecordset копирует на рабочий лист Excel только строY ки с данными, а строку заголовка необходимо создавать вручную. Результат выполнения макроса GetUnsentTransfers до отображения пользовательY ской формы показан на рис. 19.3. Рис. 19.3. Метод CopyFromRecordset копи& рует на рабочий лист Excel результат извлече& ния записей из таблицы tblTransfer Обновление записей таблицы Access Чтобы обновить существующую запись таблицы Access, создайте набор данных, содержащий однуYединственную запись. Обычно для этого необхоY димо, чтобы пользователь выбрал требуемую запись (а значит и ее уникальY ный идентификатор YYYY ключ) с помощью формы. Измените значение требуеY мого поля с помощью свойства Fields и внесите обновленную запись в базу данных с помощью метода Update. Рассмотренный ранее макрос GetUnsentTransfers помещает записи таблицы tblTransfer на рабочий лист, а затем выводит на экран пользоваY тельскую форму frmTransConf. Метод инициализации формы UserForm_Initialize загружает все записи с текущего рабочего листа в список, Использование Microsoft Access Глава 19 497 поддерживающий множественный выбор (значение свойства MultiSelect равно True): Private Sub UserForm_Initialize() OrigBook = ActiveWorkbook.Name ' Загрузить записи в список. FinalRow = Cells(65536, 1).End(xlUp).Row If FinalRow > 1 Then Me.lbXlt.RowSource = "A2:F" & FinalRow End If End Sub Форма, показанная на рис. 19.4, отображает записи, соответствующие неY отправленным товарам. Чтобы изменить значение поля Отправлен требуеY мых записей с False на True, выделите их и щелкните на кнопке Подтвердить. Рис. 19.4. Пользовательская форма отобра& жает записи, соответствующие неотправлен& ным товарам (значение поля Отправлен равно False). Чтобы отразить факт отправки товаров в базе данных, выделите требуемые записи и щелкните на кнопке Подтвердить Ниже приведен код, выполняющийся в результате щелчка на кнопке Подтвердить. Ключевым фрагментом этого кода является создание SQLY запроса, использующегося для отбора единственной записи таблицы tblTransfer с помощью поля Идентификатор. 498 Часть III Удивительные возможности Visual Basic for Applications Private Sub cbConfirm_Click() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset ' Если в списке не содержится ' ни одной записи, вывести сообщение. CountSelect = 0 For x = 0 To Me.lbXlt.ListCount - 1 If Me.lbXlt.Selected(x) Then CountSelect = CountSelect + 1 End If Next x If CountSelect = 0 Then MsgBox "Перемещаемых товаров нет. Чтобы закрыть форму, _ щелкните на кнопке Выход." Exit Sub End If ' Установить соединение с базой данных transfers.mdb. MyConn = ThisWorkbook.Path & Application.PathSeparator & _ "transfers.mdb" Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With ' Обновить записи таблицы tblTransfer. For x = 0 To Me.lbXlt.ListCount - 1 If Me.lbXlt.Selected(x) Then ThisID = Cells(2 + x, 1).Value ' Обновить запись с идентификатором ThisID. ' Создать SQL-запрос. sSQL = "SELECT * FROM tblTransfer Where _ Идентификатор=" & ThisID Set rst = New ADODB.Recordset With rst .Open Source:=sSQL, ActiveConnection:=cnn, _ CursorType:=adOpenKeyset, LockType:=adLockOptimistic ' Обновить поле. .Fields("Отправлен").Value = True .Update .Close End With End If Next x ' Закрыть объекты набора данных и соединения. cnn.Close Set rst = Nothing Set cnn = Nothing ' Закрыть пользовательскую форму. Unload Me End Sub Использование Microsoft Access Глава 19 499 Удаление записей таблицы Access Чтобы удалить существующую запись таблицы Access, создайте SQLYкод, удаляющий запись на основе значения ее уникального идентификатора. УстаY новите соединение с базой данных и выполните SQLYкод с помощью метода Execute, как показано ниже: Public Sub DeleteRecord(RecID) ' Установить соединение с базой данных transfers.mdb. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" With New ADODB.Connection .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn .Execute "Delete From tblTransfer Where _ Идентификатор = " & RecID .Close End With End Sub Создание итоговых запросов Одна из многочисленных возможностей Access заключается в создании итоговых запросов с объединением записей с одинаковыми значениями в укаY занном списке полей в одну запись. Попробуйте создать итоговый запрос и просмотрите полученный SQLYкод. Аналогичный запрос можно создать с поY мощью Excel VBA и передать его на обработку Access с помощью средств бибY лиотеки ADO. Следующий макрос выполняет сложный SQLYзапрос, возвращающий чисY тый итог перемещения товара B10894 по магазинам. Sub NetTransfers(Style As Variant) ' Этот макрос подсчитывает чистый итог ' перемещения заданного товара по магазинам. Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Создать сложный SQL-запрос. Логика запроса: объединить результаты запроса, возвращающего сумму входящих перемещений указанного товара по магазинам, с результатами запроса, возвращающего отрицательную сумму исходящих перемещений указанного товара по магазинам. sSQL = "Select Магазин, Sum(Количество), Min(мДата) From _ (SELECT ВМагазин AS Магазин, Sum(Количество) AS Количество, _ Min(Дата) AS мДата FROM tblTransfer where Товар='" & Style & "' _ AND Получен=FALSE GROUP BY ВМагазин " sSQL = sSQL & " Union All SELECT ИзМагазина AS Магазин, _ Sum(-1*Количество) AS Количество, Min(Дата) AS мДата FROM _ tblTransfer where Товар='" & Style & "' AND Отправлен=FALSE _ GROUP BY ИзМагазина)" sSQL = sSQL & " Group by Магазин" ' ' ' ' ' 500 Часть III Удивительные возможности Visual Basic for Applications MyConn = ThisWorkbook.Path & Application.PathSeparator & _ "transfers.mdb" ' Открыть соединение. Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With Set rst = New ADODB.Recordset rst.CursorLocation = adUseServer ' Выполнить SQL-запрос. rst.Open Source:=sSQL, _ ActiveConnection:=cnn, _ CursorType:=AdForwardOnly, _ LockType:=adLockOptimistic, _ Options:=adCmdText Range("A1:C1").Value = Array("Магазин", "Количество", "Дата") ' Скопировать результаты запроса на рабочий лист. Range("A2").CopyFromRecordset rst rst.Close cnn.Close End Sub Несколько полезных макросов В оставшихся разделах главы рассматриваются несколько полезных функY ций и макросов, использующих возможности библиотеки ADO. Как отмечаY лось ранее, пользователи А и Б имеют доступ к базе данных Access, располоY женной на сетевом диске, но не имеют установленной копии Microsoft Access. В связи с этим возникает потребность обновления базы данных средствами VBA, что удобно осуществлять при открытии рабочей книги Excel. Макрос обновления базы данных можно разместить в надстройке и выполнить при открытии рабочей книги с помощью обработчика события Workbook_Open (см. главу 25, “Надстройки”). Проверка существования таблицы в базе данных Access В одном из следующих разделов рассматривается добавление новой таблиY цы в существующую базу данных Access. Естественно, что добавить новую таблицу сможет только один пользователь, тот, кто первым откроет рабочую книгу и тем самым инициирует выполнение соответствующего макроса. Функция TableExists проверяет существование таблицы с указанным именем в базе данных Transfers.mdb с помощью метода OpenSchema. Function TableExists(WhichTable) Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Dim fld As ADODB.Field Использование Microsoft Access Глава 19 501 TableExists = False ' Задать путь к базе данных Transfers.mdb. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With Set rst = cnn.OpenSchema(adSchemaTables) Do Until rst.EOF If LCase(rst!Table_Name) = LCase(WhichTable) Then TableExists = True GoTo ExitMe End If rst.MoveNext Loop ExitMe: rst.Close Set rst = Nothing ' Закрыть соединение. cnn.Close End Function Проверка существования поля в таблице базы данных Access В одном из следующих разделов рассматривается добавление нового поля в таблицу базы данных Access. Функция ColumnExists проверяет существоваY ние поля с указанным именем в заданной таблице базы данных Transfers.mdb. Function ColumnExists(WhichColumn, WhichTable) Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Dim WSOrig As Worksheet Dim WSTemp As Worksheet Dim fld As ADODB.Field ColumnExists = False ' Задать путь к базе данных Transfers.mdb. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With 502 Часть III Удивительные возможности Visual Basic for Applications Set rst = cnn.OpenSchema(adSchemaColumns) Do Until rst.EOF If LCase(rst!Column_Name) = LCase(WhichColumn) And _ LCase(rst!Table_Name) = LCase(WhichTable) Then ColumnExists = True GoTo ExitMe End If rst.MoveNext Loop ExitMe: rst.Close Set rst = Nothing ' Закрыть соединение. cnn.Close End Function Добавление таблицы в базу данных Access Следующий макрос добавляет таблицу tblReplenish в базу данных Transfers.mdb с помощью команды SQL CREATE TABLE. Sub ADOCreateReplenish() ' Этот макрос создает таблицу tblReplenish ' в базе данных Transfers.mdb. ' Таблица tblReplenish имеет 5 полей. ' 1. Товар. ' 2. Кат1 - минимальный уровень запасов товара ' магазинов 1-й категории. ' 3. Кат2 - минимальный уровень запасов товара ' магазинов 2-й категории. ' 4. Кат3 - минимальный уровень запасов товара ' магазинов 3-й категории. ' 5. Активна - поле "Да/Нет". Dim cnn As ADODB.Connection Dim cmd As ADODB.Command ' Задать путь к базе данных. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" ' Открыть соединение. Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With Set cmd = New ADODB.Command Set cmd.ActiveConnection = cnn ' Создать таблицу. cmd.CommandText = "CREATE TABLE tblReplenish (Товар Char(10) _ Primary Key, Кат1 int, Кат2 int, Кат3 Int, Активна YesNo)" cmd.Execute , , adCmdText Set cmd = Nothing Использование Microsoft Access Глава 19 503 Set cnn = Nothing Exit Sub End Sub Добавление поля в таблицу базы данных Access Следующий макрос добавляет поле Группа в таблицу tblReplenish баY зы данных Transfers.mdb с помощью команды SQL ALTER TABLE. Sub ADOAddField() ' Этот макрос добавляет поле Группа к таблице tblReplenish. Dim cnn As ADODB.Connection Dim cmd As ADODB.Command ' Задать путь к базе данных. MyConn = ThisWorkbook.Path & Application.PathSeparator _ & "transfers.mdb" ' Открыть соединение. Set cnn = New ADODB.Connection With cnn .Provider = "Microsoft.Jet.OLEDB.4.0" .Open MyConn End With Set cmd = New ADODB.Command Set cmd.ActiveConnection = cnn ' Добавить поле в таблицу tblReplenish. cmd.CommandText = "ALTER TABLE tblReplenish Add Column _ Группа Char(25)" cmd.Execute , , adCmdText Set cmd = Nothing Set cnn = Nothing End Sub Следующий шаг Следующая глава посвящена созданию модулей классов, предназначенных для размещения пользовательских объектов. Глава 20 Ñîçäàíèå ïîëüçîâàòåëüñêèõ îáúåêòîâ, òèïîâ è êîëëåêöèé Несмотря на все разнообразие встроенных объектов Excel, в некоY торых ситуациях наиболее оптиY мальным решением является создаY ние собственного объекта, преднаY значенного для выполнения узкоY специализированной задачи. Excel поддерживает создание пользоваY тельских объектов в так называеY мых модулях классов. Ранее в этой книге рассматриваY лись ‘‘улучшенные’’ модули классов, такие как модуль рабочей книги, раY бочего листа, диаграммы и пользоваY тельской формы. Каждому из этих модулей свойственны характеристиY ки, уникальные для соответствуюY щего типа объектов. В частности, это касается обработчиков событий. Кроме размещения пользовательY ских объектов с соответствующими свойствами и методами, классы моY дулей могут включать в себя код обY работчиков событий уровня прилоY жения, встроенной диаграммы, элеY мента управления ActiveX и др. 20 Создание модуля класса ........... 506 Обработка событий уровня приложения и встроенной диаграммы ................................... 506 Создание пользовательского объекта ........................................... 510 Применение пользовательского объекта на практике ....................................511 Использование выражений Property Let и Property Get.......... 513 Коллекции ..................................... 515 Создание пользовательских типов .............................................. 520 Следующий шаг........................... 524 506 Часть III Удивительные возможности Visual Basic for Applications Создание модуля класса Чтобы добавить модуль класса к текущему проекту, выберите в меню реY дактора Visual Basic команду Insert Class Module (Вставить Модуль класса). Excel добавит новый модуль класса Class1, разместив его в папке Class Modules (Модули классов) текущего проекта, как показано на рис. 20.1. Рис. 20.1. Пользовательские объекты размещаются в модулях классов При работе с пользовательскими объектами необходимо помнить следуюY щие ключевые моменты. Каждый пользовательский объект должен размещаться в отдельном модуле класса. (Обработчики событий пользовательских объектов моY гут размещаться в одном модуле класса.) Модуль класса необходимо переименовать в соответствии с именем размещенного в нем пользовательского объекта. Обработка событий уровня приложения и встроенной диаграммы В главе 8, ‘‘События’’, рассматривалась обработка событий уровня рабочей книги, рабочего листа и листа диаграммы. Там же описывалось создание моY дуля класса для размещения кода обработки событий приложения и встроенY ной диаграммы. Более подробно тема обработки событий уровня приложения и встроенной диаграммы рассматривается в следующих разделах. Создание пользовательских объектов, типов и коллекций Глава 20 507 События уровня приложения Событие уровня рабочей книги Workbook_BeforePrint срабатывает при попытке печати конкретной рабочей книги. Чтобы обеспечить одинаковую обработку события Workbook_BeforePrint в нескольких рабочих книгах, скопируйте соответствующий программный код в каждую книгу или воспольY зуйтесь событием уровня приложения WorkbookBeforePrint. События уровня приложения охватывают все рабочие книги, открытые в рамках текущего сеанса работы с Excel. Для доступа к событиям уровня приY ложения необходимо использовать модуль класса, как показано ниже. 1. Добавьте модуль класса к текущему проекту. Присвойте модулю какоеY нибудь значащее имя, например, clsAppEvents. На заметку Чтобы переименовать модуль, выберите в меню редактора Visual Basic команду View Properties Window (Вид Окно свойств). 2. Разместите в модуле класса следующую строку кода: Public WithEvents xlApp As Application Имя используемой переменной (в данном случае xlApp) может быть любым. Ключевое слово WithEvents делает доступными события объекта Application на уровне модуля clsAppEvents. 3. В результате выполнения предыдущего шага переменная xlApp станет доступной из раскрывающегося списка Object (Объект) редактора Visual Basic. После выбора переменной xlApp из раскрывающегося списка Object в расположенном рядом раскрывающемся списке Procedure (Процедура) появятся события, соответствующие типу объекта этой пеY ременной (в данном случае Application), как показано на рис. 20.2. Рис. 20.2. Выбор объекта из раскрывающегося списка Object делает доступными соответст& вующие типу этого объекта события См. также События уровня приложения рассматриваются в главе 8, “События”, на с. 208. 508 Часть III Удивительные возможности Visual Basic for Applications Для каждого события, перечисленного в раскрывающемся списке Procedure, можно создать собственный код обработки. Ниже приведен код обработки соY бытия NewWorkbook, создающий нижний колонтитул рабочей книги. Код обY работчика xlApp_NewWorkbook следует поместить в модуль класса clsAppEvents после строки, содержащей объявление переменной xlApp. Private Sub xlApp_NewWorkbook(ByVal Wb As Workbook) Dim wks As Worksheet On Error Resume Next With Wb For Each wks In .Worksheets wks.PageSetup.LeftFooter = "Создано: " & _ .Application.UserName wks.PageSetup.RightFooter = Now Next wks End With End Sub В отличие от обработчиков событий уровня рабочей книги или рабочего листа, процедура, размещенная в модуле класса, не вызывается автоматичеY ски. Создайте экземпляр класса clsAppEvents и установите значение его свойства xlApp равным Application, как показано ниже. Public myAppEvent As clsAppEvents Sub TrapAppEvent() Set myAppEvent.xlApp = Application End Sub После выполнения процедуры TrapAppEvent обработчик xlApp_NewWorkbook будет формировать нижний колонтитул для каждой новой рабочей книги, созданной в рамках текущего сеанса работы с Excel (рис. 20.3). Рис. 20.3. Обработка события уровня приложения NewWorkbook позволяет формировать нижний колонтитул для каждой новой рабочей книги, созданной в рамках текущего сеан& са работы с Excel Внимание Обработка событий уровня приложения может быть приостановлена вследствие выполнения действий, приводящих к обнулению значений переменных модуля. В частности, к таким действиям относится изменение программного кода в ре& дакторе Visual Basic. Чтобы возобновить обработку событий уровня приложения, воссоздайте соответствующие объекты (в данном случае для этого достаточно вы& полнить процедуру TrapAppEvent). Создание пользовательских объектов, типов и коллекций Глава 20 509 Объявление переменной myAppEvent, а также процедура TrapAppEvent размещены в стандартном модуле. Чтобы автоматизировать обработку собыY тия уровня приложения, модули классов можно перенести в личную книгу мак* росов (PERSONAL.XLS), а вызов процедуры TrapAppEvent — в обработчик события Workbook_Open. Объявление переменной myAppEvent должно остаY ваться в стандартном модуле, чтобы переменная была доступна другим модулям. События встроенной диаграммы Обработка событий встроенной диаграммы аналогична обработке событий уровня приложения. Добавьте модуль класса и разместите в нем объявление общедоступной переменной типа Char. Затем создайте код обработки требуеY мого события и разместите в стандартном модуле процедуру, инициирующую обработку событий встроенной диаграммы. На заметку Код обработки событий уровня приложения и событий встроенной диаграммы можно размещать в одном и том же модуле класса. Разместите в модуле класса следующую строку кода: Public WithEvents xlChart As Chart Переменная xlChart станет доступной из раскрывающегося списка Object (Объект) редактора Visual Basic. После выбора переменной xlChart из раскрывающегося списка Object в расположенном рядом раскрывающемся списке Procedure (Процедура) появятся события, соответствующие типу объекта этой переменной (в данном случае Chart), как показано на рис. 20.4. Рис. 20.4. Выбор переменной xlChart из раскрывающегося списка Object делает доступными события встроенной диаграммы См. также События уровня листа диаграммы рассматриваются в главе 8, “События”, на с. 204. Создадим обработчик события MouseDown, изменяющий масштаб диаY граммы при щелчке на ее области кнопкой мыши. Для этого потребуется пеY 510 Часть III Удивительные возможности Visual Basic for Applications реопределить стандартный способ обработки еще двух событий YYYY BeforeRightClick и BeforeDoubleClick. Следующий код предотвращает выполнение стандартных действий, предY принимаемых при обработке двойного щелчка и щелчка правой кнопкой мыши: Private Sub xlChart_BeforeDoubleClick(ByVal ElementID As Long, _ ByVal Arg1 As Long, ByVal Arg2 As Long, Cancel As Boolean) Cancel = True End Sub Private Sub xlChart_BeforeRightClick(Cancel As Boolean) Cancel = True End Sub Определим способ обработки события, срабатывающего в результате нажаY тия любой кнопки мыши. Private Sub xlChart_MouseDown(ByVal Button As Long, _ ByVal Shift As Long, ByVal x As Long, ByVal y As Long) If Button = 1 Then ActiveChart.Axes(xlValue).MaximumScale = _ ActiveChart.Axes(xlValue).MaximumScale - 50 End If If Button = 2 Then ActiveChart.Axes(xlValue).MaximumScale = _ ActiveChart.Axes(xlValue).MaximumScale + 50 End If End Sub Создайте экземпляр класса clsChartEvents и установите значение его свойства xlChart, как показано ниже (рис. 20.5). Public myChartEvent As clsChartEvents Sub TrapChartEvent() Set myChartEvent.xlChart = Worksheets("Встроенная _ диаграмма").ChartObjects("Chart 1").Chart End Sub Создание пользовательского объекта Кроме кода обработчиков событий, модули классов могут содержать польY зовательские объекты с определением соответствующих свойств и методов. Создадим объект служащего с такими характеристиками, как имя, идентифиY катор, тарифная ставка и количество рабочих часов. Добавьте модуль класса и переименуйте его в clsEmployee. Ниже переY числены свойства объекта clsEmployee. EmpName — имя служащего; EmpID — идентификатор служащего; EmpRate — тарифная ставка; EmpWeeklyHrs — количество рабочих часов в неделю. Создание пользовательских объектов, типов и коллекций Глава 20 Рис. 20.5. Обработка событий встроенной диаграммы реализуется посредством использова& ния модуля класса При объявлении свойств и переменных указывается область видимости YYYY обычно Private или Public. Поскольку свойства объекта clsEmployee должны быть доступны из стандартного модуля, сделайте их общими, размесY тив следующие строки в начале модуля класса: Public Public Public Public EmpName As String EmpID As String EmpRate As Double EmpWeeklyHrs As Double Методы объекта YYYY это ‘‘действия’’, которые он может выполнять. В модуY ле класса ‘‘действия’’ объекта представлены процедурами и функциями. СлеY дующий метод подсчитывает недельную заработную плату служащего: Public Function EmpWeeklyPay() As Double EmpWeeklyPay = EmpRate*EmpWeeklyHrs End Function Создание объекта clsEmployee, содержащего четыре свойства и один меY тод, можно считать завершенным. В следующем разделе рассматривается применение пользовательского объекта на практике. Применение пользовательского объекта на практике Пользовательский объект, корректно заданный в модуле класса, становитY ся доступным для других модулей. Ниже приведен пример объявления переY менной типа clsEmployee: Dim Employee As clsEmployee Следующий код создает экземпляр пользовательского объекта: Set Employee = New clsEmployee 511 512 Часть III Удивительные возможности Visual Basic for Applications При разработке программного кода Excel отображает динамические подY сказки, упрощающие обращение к свойствам и методам пользовательского объекта (рис. 20.6). Рис. 20.6. В аспекте отображения динамических подсказок пользовательские объекты ничем не отличаются от стандартных объектов Excel Приведенный ниже код наглядно демонстрирует применение пользоваY тельского объекта на практике. Option Explicit Dim Employee As clsEmployee Sub EmpPay() Set Employee = New clsEmployee With Employee .EmpName = "Трейси Сирстад" .EmpID = "1651" .EmpRate = 25 .EmpWeeklyHrs = 40 MsgBox .EmpName & " зарабатывает в неделю " & _ .EmpWeeklyPay & " долларов." End With End Sub Макрос EmpPay создает экземпляр пользовательского объекта clsEmployee и задает значения всех его свойств. После этого макрос выводит на экран сообщение, содержащее сведения о недельной заработной плате слуY жащего, как показано на рис. 20.7. Создание пользовательских объектов, типов и коллекций Глава 20 513 Рис. 20.7. При подсчете недельной за& работной платы служащего использует& ся метод EmpWeeklyPay объекта clsEmployee Использование выражений Property Let и Property Get Значения общедоступных переменных (таких как EmpName, EmpID, EmpRate и EmpWeeklyHrs) можно считывать и изменять произвольным образом. Если доступ к свойствам объекта необходимо ограничить, воспользуйтесь выY ражениями Property Let и Property Get. Выражение Property Let позволяет управлять изменением свойств объекY та, а выражение Property Get — считыванием этих свойств. В рассмотренY ном ранее примере для хранения информации о количестве рабочих часов в неделю использовалась общедоступная переменная EmpWeeklyHrs, что не позволяло вести учет сверхурочных часов. Внесем изменения в модуль класса, добавив к объекту clsEmployee два новых свойства YYYY EmpNormalHrs и EmpOverTimeHrs. Поскольку эти свойY ства должны быть доступны только для чтения, их нельзя реализовать с поY мощью переменных с областью видимости Public. Вместо этого воспользуY емся выражением Property Get. Установка значения свойств EmpNormalHrs и EmpOverTimeHrs будет производиться с помощью свойства EmpWeeklyHrs, которое также нельзя реализовать в виде общедоступной переменной. Создадим две закрытые переY менные NormalHrs и OverHrs, доступные для считывания и изменения исY ключительно в пределах модуля класса. Public EmpName As String Public EmpID As String Public EmpRate As Double Private NormalHrs As Double Private OverHrs As Double Выражение Property Let позволяет использовать свойство EmpWeeklyHrs для установки значения закрытых переменных NormalHrs и OverHrs. Property Let EmpWeeklyHrs(Hrs As Double) NormalHrs = WorksheetFunction.Min(40, Hrs) OverHrs = WorksheetFunction.Max(0, Hrs - 40) End Property 514 Часть III Удивительные возможности Visual Basic for Applications Выражение Property Get EmpWeeklyHrs определяет результат считыY вания значения свойства EmpWeeklyHrs как сумму переменных NormalHrs и OverHrs. Property Get EmpWeeklyHrs() As Double EmpWeeklyHrs = NormalHrs + OverHrs End Property Выражение Property Get используется для определения результатов считывания значений свойств EmpNormalHrs и EmpOverTimeHrs, как покаY зано ниже: Property Get EmpNormalHrs() As Double EmpNormalHrs = NormalHrs End Property Property Get EmpOverTimeHrs() As Double EmpOverTimeHrs = OverHrs End Property Поскольку свойства EmpNormalHrs и EmpOverTimeHrs должны быть доступны только для чтения, их значения устанавливаются неявно посредстY вом свойства EmpWeeklyHrs. Наконец, ниже приведен код обновленного метода EmpWeeklyPay: Public Function EmpWeeklyPay() As Double EmpWeeklyPay = (EmpNormalHrs * EmpRate) + _ (EmpOverTimeHrs * EmpRate * 1.5) End Function Макрос EmpPayOverTime использует преимущества, полученные в реY зультате внесения изменений в модуль класса. Option Explicit Dim Employee As clsEmployee Sub EmpPayOverTime() Set Employee = New clsEmployee With Employee .EmpName = "Трейси Сирстад" .EmpID = "1651" .EmpRate = 25 .EmpWeeklyHrs = 45 MsgBox .EmpName & Chr(10) & Chr(9) & _ "Стандартные часы: " & .EmpNormalHrs & Chr(10) & Chr(9) & _ "Сверхурочные часы: " & .EmpOverTimeHrs & Chr(10) & Chr(9) & _ "Заработная плата за неделю (долларов): " & .EmpWeeklyPay End With End Sub Результат выполнения макроса EmpPayOverTime показан на рис. 20.8. Создание пользовательских объектов, типов и коллекций Глава 20 515 Рис. 20.8. Выражения Property Let и Property Get позволяют ограничить доступ к свойствам пользовательского объекта Коллекции Коллекция предназначена для представления нескольких экземпляров объекта. Например, объект Worksheet входит в коллекцию Worksheets. Коллекция позволяет обращаться к любому ее элементу, подсчитывать общее количество элементов в коллекции, а также удалять и добавлять элементы. Аналогичная функциональность доступна для пользовательских объектов. Существует два способа создания коллекции: в стандартном модуле и в моY дуле класса. Создание коллекции в стандартном модуле Самый простой способ создания коллекции заключается в ее размещении в стандартном модуле и использовании встроенного объекта VBA Collection. Это позволяет получить доступ к четырем основным методам и свойстY вам коллекции: Add, Remove, Count и Item. Следующий макрос считывает список служащих с рабочего листа и помеY щает их в массив. В результате обработки полученного массива создается колY лекция пользовательских объектов clsEmployee. Sub EmpPayCollection() Dim colEmployees As New Collection Dim recEmployee As New clsEmployee Dim LastRow As Integer, myCount As Integer Dim EmpArray As Variant LastRow = ActiveSheet.Range("A65536").End(xlUp).Row EmpArray = ActiveSheet.Range(Cells(1, 1), Cells(LastRow, 4)) For myCount = 1 To UBound(EmpArray) With recEmployee .EmpName = EmpArray(myCount, 1) .EmpID = EmpArray(myCount, 2) .EmpRate = EmpArray(myCount, 3) .EmpWeeklyHrs = EmpArray(myCount, 4) colEmployees.Add recEmployee, .EmpID End With Next myCount 516 Часть III Удивительные возможности Visual Basic for Applications MsgBox "Общее число служащих: " & colEmployees.Count & _ Chr(10) & "Имя 2-го служащего в коллекции: " & _ colEmployees(2).EmpName MsgBox "Недельная заработная плата Трейси (долларов): " & _ colEmployees("1651").EmpWeeklyPay Set recEmployee = Nothing End Sub Коллекция colEmployees создается как экземпляр объекта Collection, а ее элементы (recEmployee) YYYY как экземпляры пользовательского объекта clsEmployee. После задания значений всех свойств объект recEmployee добавляется в коллекцию. Второй параметр метода Add определяет уникальный ключ запиY си в коллекции YYYY в данном случае это идентификатор служащего. Наличие уникального ключа позволяет использовать сокращенную форму обращения к записи, например, colEmployees("1651").EmpWeeklyPay (рис. 20.9). Рис. 20.9. Уникальный ключ позволяет использо& вать сокращенную форму обращения к записи в коллекции На заметку Уникальный ключ записи является необязательным параметром метода Add. В од& ной коллекции не могут существовать две записи с одинаковым значением уни& кального ключа. При попытке добавления записи, приводящей к дублированию уникальных ключей, будет сгенерировано сообщение об ошибке. Создание коллекции в модуле класса При создании коллекции в модуле класса ее основные методы и свойства (Add, Remove, Count и Item) необходимо определять самостоятельно. ПреY имущества этого способа создания коллекции заключаются в размещении коY да коллекции в одном модуле, возможности полного контроля над ее ‘‘поведением’’, а также возможности управления доступом к коллекции. Добавьте к текущему проекту новый модуль класса и переименуйте его в clsEmployees. Объявите закрытую переменную типа Collection, которая будет использоваться только в пределах данного модуля класса. Private AllEmployees As New Collection Создание пользовательских объектов, типов и коллекций Глава 20 517 Определите основные свойства и методы коллекции. Для этого воспольY зуйтесь соответствующими свойствами и методами объекта Collection, доступными в пределах модуля clsEmployees. Следующий код определяет метод добавления новых элементов в коллекY цию Add. Public Sub Add(recEmployee As clsEmployee) AllEmployees.Add recEmployee, recEmployee.EmpID End Sub Свойство Count возвращает общее число элементов в коллекции. Public Property Get Count() As Long Count = AllEmployees.Count End Property Свойство Items используется для обращения ко всей коллекции. Public Property Get Items() As Collection Set Items = AllEmployees End Property Свойство Item предназначено для обращения к отдельному элементу колY лекции. Public Property Get Item(myItem As Variant) As clsEmployee Set Item = AllEmployees(myItem) End Property Наконец, метод Remove предназначен для удаления из коллекции заданY ного элемента. Public Sub Remove(myItem As Variant) AllEmployees.Remove myItem End Sub При необходимости функциональность основных методов коллекции можно улучшить, например, реализовать автоматическое создание уникальY ного ключа записи в методе Add. Предназначенные только для чтения свойства Count, Items и Item опреY деляются с помощью выражения Property Get. Свойство Item позволяет обратиться к одному элементу коллекции, а свойство Items — ко всей колY лекции целиком (в частности, это свойство удобно применять в цикле For Each...Next). Следующий макрос аналогичен рассмотренному ранее макросу EmpPayCollection за исключением того, что теперь коллекция пользовательских объектов clsEmployee размещается не в стандартном модуле, а в модуле класса. Sub EmpAddCollection() Dim colEmployees As New clsEmployees Dim recEmployee As New clsEmployee Dim LastRow As Integer, myCount As Integer Dim EmpArray As Variant LastRow = ActiveSheet.Range("A65536").End(xlUp).Row EmpArray = ActiveSheet.Range(Cells(1, 1), Cells(LastRow, 4)) For myCount = 1 To UBound(EmpArray) 518 Часть III Удивительные возможности Visual Basic for Applications With recEmployee .EmpName = EmpArray(myCount, 1) .EmpID = EmpArray(myCount, 2) .EmpRate = EmpArray(myCount, 3) .EmpWeeklyHrs = EmpArray(myCount, 4) End With colEmployees.Add recEmployee Next myCount MsgBox "Общее число служащих: " & colEmployees.Count & _ Chr(10) & "Имя 2-го служащего в коллекции: " & _ colEmployees.Item(2).EmpName MsgBox "Недельная заработная плата Трейси (долларов): " & _ colEmployees.Item("1651").EmpWeeklyPay For Each recEmployee In colEmployees.Items recEmployee.EmpRate = recEmployee.EmpRate * 1.5 Next recEmployee MsgBox "Недельная заработная плата Трейси с премиальными _ (долларов): " & colEmployees.Item("1651").EmpWeeklyPay End Sub Обратите внимание, что переменная colEmployees имеет тип clsEmployees, а не тип Collection. Способ заполнения массива и коллекции осY тался прежним, а вот способ обращения к элементам коллекции претерпел некоторые изменения. В частности, для обращения ко второму элементу колY лекции необходимо использовать свойство Item. Сравните вызовы метода MsgBox в макросах EmpPayCollection и EmpAddCollection. Цикл For Each...Next используется для увеличения значения свойства EmpRate всех записей коллекции в 1,5 раза. Результат последнего вызова меY тода MsgBox в макросе EmpAddCollection показан на рис. 20.10. Рис. 20.10. При создании коллекции в модуле класса ее методы и свойства необходимо определять самостоятельно Практикум Кнопки получения справочной информации Рассмотрим задачу предоставления справочной информации по содержимому рабочего листа. Несмотря на то, что ее наиболее очевидное решение заключается в использовании примечаний ячеек, это может смутить новичка Excel. Другой спо& Создание пользовательских объектов, типов и коллекций Глава 20 519 соб состоит в создании кнопок, щелчок на каждой из которых приводит к отобра& жению формы, содержащей справочные сведения. Разместите на рабочем листе три небольшие надписи, содержащие знак вопроса (по одной надписи на строку). Внешний вид надписей, показанных на рис. 20.11, был достигнут за счет установки значения свойства надписи SpecialEffect рав& ным fmSpecialEffectRaised, а свойства BackColor — равным Button Face. Поместите текст справки на два столбца правее соответствующей этому тексту кнопки (надписи), после чего скройте столбец справочной информации. Рис. 20.11. Разместите на рабочем листе кнопки (надписи) и соответствующую им справоч& ную информацию Создайте пользовательскую форму, содержащую надпись и кнопку Закрыть справку, как показано на рис. 20.12. Рис. 20.12. Создайте пользовательскую форму, которая будет применяться для вывода справочной информации на экран Переименуйте форму в HelpForm, кнопку — в CloseHelp, а надпись — в HelpText. Измените размер надписи таким образом, чтобы в ней мог поместить& ся текст справки. Следующий макрос выгружает форму HelpForm из памяти при щелчке на кнопке CloseHelp. Private Sub CloseHelp_Click() Unload Me End Sub Добавьте к проекту модуль класса clsLabel. Объявите переменную Lbl для об& ращения к событиям объекта Label. Public WithEvents Lbl As MSForms.Label Следующая процедура выводит на экран пользовательскую форму с соответст& вующим текстом справки. Private Sub Lbl_Click() Dim Rng As Range 520 Часть III Удивительные возможности Visual Basic for Applications Set Rng = Lbl.TopLeftCell If Lbl.Caption = "?" Then HelpForm.Caption = "Надпись в ячейке " & Rng.Address(0, 0) HelpForm.HelpText.Caption = Rng.Offset(, 2).Value HelpForm.Show End If End Sub Разместите в модуле ЭтаКнига (ThisWorkbook) следующий код: Dim col As Collection Private Dim Dim Dim Sub DefineLabels() WS As Worksheet cLbl As clsLabel OleObj As OLEObject Set col = New Collection For Each WS In ThisWorkbook.Worksheets For Each OleObj In WS.OLEObjects If OleObj.OLEType = xlOLEControl Then If TypeName(OleObj.Object) = "Label" Then Set cLbl = New clsLabel Set cLbl.Lbl = OleObj.Object col.Add cLbl End If End If Next OleObj Next WS End Sub Выполните процедуру DefineLabels для создания коллекции кнопок получения справочной информации. Результат щелчка на одной из таких кнопок показан на рис. 20.13. Рис. 20.13. Результат щелчка на кнопке получения справочной информации Создание пользовательских типов В отличие от пользовательских объектов, пользовательские типы не требуY ют своего размещения в модуле класса. Модуль класса позволяет определить свойства и методы объекта, тогда как пользовательский тип характеризуется только свойствами. Создание пользовательских объектов, типов и коллекций Глава 20 521 Пользовательский тип объявляется с помощью выражения Type...End Type и может быть общедоступным (Public) или закрытым (Private). ПоY добно объекту, у пользовательского типа есть имя и свойства, которые задаются с помощью переменных, объявленных внутри выражения Type...End Type. Применение пользовательских типов на практике ничем не отличается от применения стандартных типов VBA. Как показано на рис. 20.14, Excel отоY бражает динамическую подсказку, упрощающую обращение к свойствам пеY ременной пользовательского типа при наборе программного кода. Рис. 20.14. Применение пользовательских ти& пов на практике ничем не отличается от приме& нения стандартных типов VBA Рассмотрим применение двух пользовательских типов для создания отчета о продажах товаров в сети розничных магазинов. Тип Style используется для представления товара. Public Type Style StyleName As String Price As Single UnitsSold As Long UnitsOnHand As Long End Type Тип Store имеет два свойства YYYY имя магазина и массив товаров, каждый из которых представлен переменной типа Style. Public Type Store Name As String Styles() As Style End Type Следующий макрос создает итоговый отчет о продажах товаров в сети розY ничных магазинов. В приведенном ниже коде используется только переменY ная типа Store, так как этот тип включает в себя свойство типа Style. 522 Часть III Удивительные возможности Visual Basic for Applications Sub UDTMain() Dim FinalRow As Long, ThisRow As Long, ThisStore As Long Dim CurrRow As Long, TotalDollarsSold As Long, _ TotalUnitsSold As Long Dim TotalDollarsOnHand As Long, TotalUnitsOnHand As Long Dim ThisStyle As Long Dim StoreName As String ' Объявление переменной пользовательского типа. ReDim Stores(0 To 0) As Store FinalRow = Range("A65536").End(xlUp).Row Следующий цикл For...Next заполняет два массива. Внешний массив (массив переменных типа Store) состоит из названий магазинов и перечня имеющихся в них товаров. Внутренний массив (массив переменных типа Style) состоит из подробных сведений о товарах. For ThisRow = 2 To FinalRow StoreName = Range("A" & ThisRow).Value ' Проверить, есть ли во внешнем массиве хотя бы один элемент. If LBound(Stores) = 0 Then ThisStore = 1 ReDim Stores(1 To 1) As Store Stores(1).Name = StoreName ReDim Stores(1).Styles(0 To 0) As Style Else For ThisStore = LBound(Stores) To UBound(Stores) If Stores(ThisStore).Name = StoreName Then Exit For Next ThisStore If ThisStore > UBound(Stores) Then ReDim Preserve Stores(LBound(Stores) To _ UBound(Stores) + 1) As Store Stores(ThisStore).Name = StoreName ReDim Stores(ThisStore).Styles(0 To 0) As Style End If End If With Stores(ThisStore) If LBound(.Styles) = 0 Then ReDim .Styles(1 To 1) As Style Else ReDim Preserve .Styles(LBound(.Styles) To _ UBound(.Styles) + 1) As Style End If With .Styles(UBound(.Styles)) .StyleName = Range("B" & ThisRow).Value .Price = Range("C" & ThisRow).Value .UnitsSold = Range("D" & ThisRow).Value .UnitsOnHand = Range("E" & ThisRow).Value End With End With Next ThisRow ' ' ' ' ' ' Создание отчета на новом рабочем листе. Sheets.Add Range("A1:E1").Value = Array("Магазин", "Продано (шт.)", _ "Продано (ден. ед.)", "Остаток (шт.)", "Остаток (ден. ед.)") CurrRow = 2 For ThisStore = LBound(Stores) To UBound(Stores) With Stores(ThisStore) TotalDollarsSold = 0 Создание пользовательских объектов, типов и коллекций Глава 20 523 TotalUnitsSold = 0 TotalDollarsOnHand = 0 TotalUnitsOnHand = 0 ' Подсчет суммарных сведений о продажах товаров. For ThisStyle = LBound(.Styles) To UBound(.Styles) With .Styles(ThisStyle) TotalDollarsSold = TotalDollarsSold + _ .UnitsSold * .Price TotalUnitsSold = TotalUnitsSold + .UnitsSold TotalDollarsOnHand = TotalDollarsOnHand + _ .UnitsOnHand * .Price TotalUnitsOnHand = TotalUnitsOnHand + _ .UnitsOnHand End With Next ThisStyle Range("A" & CurrRow & ":E" & CurrRow).Value = _ Array(.Name, TotalUnitsSold, TotalDollarsSold, _ TotalUnitsOnHand, TotalDollarsOnHand) End With CurrRow = CurrRow + 1 Next ThisStore End Sub Результат выполнения макроса UDTMain показан на рис. 20.15. Рис. 20.15. Пользовательские типы позволяют упростить разработку достаточно сложного программного кода. (Примечание: для наглядно& сти результаты выполнения макроса UDTMain совмещены с исходны& ми данными.) 524 Часть III Удивительные возможности Visual Basic for Applications Следующий шаг Следующая глава посвящена пользовательским формам. В ней описываютY ся различные элементы управления, а также приемы программирования польY зовательских форм. Глава 21 Ïîëüçîâàòåëüñêèå ôîðìû — ïðîôåññèîíàëüíûé ïîäõîä В главе 9, ‘‘Введение в пользоваY тельские формы’’, рассматривались основы создания пользовательских форм. Продолжим знакомство с польY зовательскими формами, уделив особое внимание сложным элеменY там управления, а также различным приемам программирования польY зовательских форм. Панель инструментов UserForm Чтобы отобразить панель инструY ментов UserForm, выберите в меню редактора Visual Basic команду View Toolbars UserForm (Вид Панели инструментов UserForm). Панель инструментов UserForm содержит неY сколько элементов управления, как поY казано на рис. 21.1. На передний план Группировать Выравнивание Уравнять размеры Масштаб Центрирование Отменить группирование На задний план Рис. 21.1. Панель инструментов UserForm 21 Панель инструментов UserForm .........................................525 Создание коллекций элементов управления формы ........................................... 526 Дополнительные элементы управления формы..................... 528 Немодальные формы ................. 531 Гиперссылки в формах ............... 531 Добавление элементов управления на форму во время выполнения программного кода.....................532 Использование полосы прокрутки для выбора значений ....................................... 539 Добавление подсказки к элементу управления.................. 541 Порядок переноса фокуса.......... 541 Изменение цвета фона активного элемента управления ................................... 542 Использование эффекта прозрачности формы................. 545 Следующий шаг........................... 546 526 Часть III Удивительные возможности Visual Basic for Applications Кнопка Bring To Front (На передний план). Вынести выбранный элемент управления поверх всех остальных элементов управления формы. Кнопка Send To Back (На задний план). Спрятать выбранный элемент управления за всеми остальными элементами управления формы. Кнопка Group (Группировать). Объединить выбранные элементы управY ления в группу. Кнопка Ungroup (Отменить группирование). Отменить объединение выY бранных элементов управления в группу. Раскрывающийся список Align (Выравнивание). Выровнять выбранные элементы управления по левому краю (Lefts), по центру (Centers), по правому краю (Rights), по верхнему краю (Tops), посередине (Middles), по нижнему краю (Bottoms) или по сетке (To Grid). Раскрывающийся список Center (Центрирование). Центрировать выY бранные элементы управления относительно формы по горизонтали (Horizontally) или по вертикали (Vertically). Раскрывающийся список Make Same Size (Уравнять размеры). Уравнять ширину (Width), высоту (Height) или оба измерения (Both) выбранных элементов управления. Раскрывающийся список Zoom (Масштаб). Масштабировать элементы управления формы. Совет Чтобы выбрать несколько смежных элементов управления, щелкните на первом и на последнем из них, удерживая нажатой клавишу <Shift>. Чтобы выбрать не& сколько несмежных элементов управления, щелкните на каждом из них, удержи& вая нажатой клавишу <Ctrl>. Создание коллекций элементов управления формы В главе 20, ‘‘Создание пользовательских объектов, типов и коллекций’’, описывалось создание коллекции надписей, использующихся в качестве кноY пок получения справочной информации. Рассмотрим пример создания колY лекции других элементов управления формы YYYY флажков. Разместите следующий код в модуле класса clsFormCtl. Public WithEvents chb As MSForms.CheckBox Public Sub SelectAll() chb.Value = True End Sub Public Sub UnselectAll() Пользовательские формы — профессиональный подход Глава 21 527 chb.Value = False End Sub Метод SelectAll выделяет флажок путем установки значения свойства Value равным True. Метод UnselectAll отменяет выделение флажка путем установки значения свойства Value равным False. Объекты флажков помещаются в коллекцию при инициализации формы frmSelectAll. Создание коллекции упрощается за счет того, что все флажки являются частью панели frm_Selection. Dim col_Selection As New Collection Private Sub UserForm_Initialize() For Each ctl In frm_Selection.Controls Set clFormCtl = New clsFormCtl Set clFormCtl.chb = ctl col_Selection.Add clFormCtl Next ctl End Sub Следующий код выделяет все флажки коллекции при щелчке на надписи Выделить все. Private Sub lbl_SelectAll_Click() For Each clFormCtl In col_Selection clFormCtl.SelectAll Next clFormCtl End Sub Процедура lbl_UnselectAll_Click, выполняющаяся при щелчке на кнопке Отменить выделение, отменяет выделение флажков коллекции. Private Sub lbl_UnselectAll_Click() For Each clFormCtl In col_Selection clFormCtl.UnselectAll Next clFormCtl End Sub Пользовательская форма frmSelectAll показана на рис. 21.2. Рис. 21.2. Использование коллекций и панелей упро& щает работу с элементами управления формы 528 Часть III Удивительные возможности Visual Basic for Applications На заметку Объединение флажков в коллекцию никоим образом не сказывается на их функ& циональности. Другими словами, флажки формы frmSelectAll по&прежнему можно устанавливать/сбрасывать по отдельности. На заметку Каждый элемент управления формы имеет свойство Tag типа String, содержа& щее дополнительную информацию об элементе управления. Это свойство можно использовать для неформального группирования элементов управления, входя& щих в другие группы. Дополнительные элементы управления формы В этом разделе продолжается рассмотрение элементов управления пользоY вательской формы, начатое в главе 9, ‘‘Введение в пользовательские формы’’. Переключатели В отличие от флажков, переключатели поддерживают возможность выбора только одного элемента из группы, как показано на рис. 21.3. Рис. 21.3. Переключатели поддерживают возможность выбора только одного элемента из группы Возможность выбора только одного элемента из группы может быть реалиY зована с помощью программного кода, однако переключатели изначально поддерживают такую функциональность благодаря наличию свойства GroupName. Все переключатели с одинаковым значением свойства GroupName принадлежат к одной группе. Выделение одного из переключателей группы автоматически приводит к отмене выделения остальных переключателей группы. Чтобы иметь возможность выбора нескольких переключателей одноY временно, присвойте им разное значение свойства GroupName или же оставьY те его незаданным. Пользовательские формы — профессиональный подход Глава 21 529 Набор вкладок Вкладки, рассматривавшиеся в главе 9, ‘‘Введение в пользовательские формы’’, позволяют объединить воедино несколько страниц с различными элементами управления. Набор вкладок (tabstrip) позволяет объединить неY сколько страниц с одинаковыми элементами управления, общими для всех входящих в набор вкладок. При переключении вкладок значения элементов управления меняются, как показано на рис. 21.4. Рис. 21.4. Набор вкладок позволяет объединить несколько страниц с одинаковыми элементами управления См. также Более подробно вкладки рассматриваются в разделе “Использование вкладок для объединения форм” главы 9 на с. 225. По умолчанию набор состоит из двух вкладок. Чтобы добавить, удалить, переименовать или переместить вкладку, щелкните на ней правой кнопкой мыши. Измените размер набора вкладок так, чтобы он мог вместить все необY ходимые элементы управления. На заметку Кнопку закрытия пользовательской формы следует разместить за пределами на& бора вкладок. Свойство набора вкладок TabOrientation позволяет определить распоY ложение корешков вкладок, которые могут располагаться вдоль верхней (fmTabOrientationTop), нижней (fmTabOrientationBottom), левой (fmTabOrientationLeft) или правой (fmTabOrientationRight) граниY цы пользовательской формы. Следующий код создает набор вкладок, показанный на рис. 21.4. Для установки значений элементов управления вкладки используется процедура SetValuesToTabStrip. Private Sub UserForm_Initialize() ' По умолчанию при инициализации формы 530 Часть III Удивительные возможности Visual Basic for Applications ' отображается содержимое первой вкладки. SetValuesToTabStrip 1 End Sub Процедура TabStrip1_Change выполняется при выборе новой вкладки, что приводит к автоматическому изменению значений элементов управления вкладки в соответствии с ее порядковым номером. Private Sub TabStrip1_Change() Dim lngRow As Long lngRow = TabStrip1.Value + 1 SetValuesToTabStrip lngRow End Sub Ниже приведен код процедуры SetValuesToTabStrip. Private Sub SetValuesToTabStrip(ByVal lngRow As Long) lbl_Address.Caption = Cells(lngRow, 2).Value lbl_Phone.Caption = Cells(lngRow, 3).Value lbl_Fax.Caption = Cells(lngRow, 4).Value lbl_Email.Caption = Cells(lngRow, 5).Value lbl_Website.Caption = Cells(lngRow, 6).Value End Sub Совет Добавить дополнительный элемент управления к вкладке, входящей в набор, можно только программным путем в момент активизации этой вкладки. Поле ввода адреса диапазона ячеек Поле ввода адреса диапазона ячеек можно разместить на любой пользоваY тельской форме. Щелчок на кнопке, расположенной справа от поля ввода, приводит к скрытию формы и отображению окна ввода адреса диапазона ячеY ек, знакомого большинству пользователей по многочисленным мастерам ExY cel. Чтобы вернуть форму на экран, щелкните на кнопке с изображением формы, расположенной в правой части окна. Следующая процедура форматирует содержимое диапазона ячеек, адрес которого был задан с помощью поля ввода, изображенного на рис. 21.5, путем утолщения шрифта. Private Sub cb1_Click() Range(RefEdit1.Value).Font.Bold = True End Sub Рис. 21.5. Окно ввода адреса диапазона ячеек знакомо большинству пользователей Excel Пользовательские формы — профессиональный подход Глава 21 531 Немодальные формы По умолчанию пользовательская форма является модальной, т.е. такой, коY торая не позволяет вернуться к просмотру или изменению содержимого рабоY чего листа Excel до тех пор, пока она не будет закрыта. Чтобы сделать форму немодальной, установите значение ее свойства ShowModal равным False. При отображении на экране немодальной формы пользователь может проY должать работу с содержимым листа Excel без какихYлибо ограничений, как показано на рис. 21.6. Рис. 21.6. Немодальная форма позволяет продолжать работу с листом Excel Гиперссылки в формах Пользовательская форма, показанная на рис. 21.4, содержит надписи с адY ресом электронной почты и адресом WebYсайта. Рассмотрим код, необходиY мый для запуска соответствующего приложения при щелчке на каждой из этих надписей. Разместите в верхней части модуля объявления требуемых функций API и переменных. Private Declare Function ShellExecute Lib "shell32.dll" _ Alias "ShellExecuteA" (ByVal hWnd As Long, ByVal lpOperation _ As String, ByVal lpFile As String, ByVal lpParameters As String, _ ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long Const SWNormal = 1 532 Часть III Удивительные возможности Visual Basic for Applications Процедура lbl_Email_Click обрабатывает щелчок на надписи с адресом электронной почты (рис. 21.7). Private Sub lbl_Email_Click() Dim lngRow As Long lngRow = TabStrip1.Value + 1 ShellExecute 0&, "open", "mailto:" & Cells(lngRow, 5).Value, _ vbNullString, vbNullString, SWNormal End Sub Рис. 21.7. Всего лишь несколько строк кода способны превратить надписи с адресом электронной почты и адресом Web&сайта в гиперссылки Процедура lbl_Website_Click обрабатывает щелчок на надписи с адреY сом WebYсайта. Private Sub lbl_Website_Click() Dim lngRow As Long lngRow = TabStrip1.Value + 1 ShellExecute 0&, "open", Cells(lngRow, 6).Value, _ vbNullString, vbNullString, SWNormal End Sub Добавление элементов управления на форму во время выполнения программного кода Рассмотрим простую форму, показанную на рис. 21.8. Эта форма, содержащая всего лишь одну кнопку Закрыть, будет использоY вана для отображения рисунков из некоторого каталога. Рисунки и соответстY вующие им надписи добавляются на форму во время выполнения программY ного кода. Пользовательские формы — профессиональный подход Глава 21 533 Рис. 21.8. Эта форма будет использована для добавления на нее элементов управ& ления во время выполнения программ& ного кода Подобная форма может применяться торговыми представителями для выY вода на экран изображений товаров из каталога. Для этого достаточно выбрать названия или коды нужных товаров на рабочем листе Excel и щелкнуть на кнопке, отображающей форму. На рис. 21.9 приведен пример вывода на экран нескольких фотографий. Рис. 21.9. Рисунки и соответствующие им надписи добавляются на форму при выполнении процедуры инициализации UserForm_Initialize 534 Часть III Удивительные возможности Visual Basic for Applications При выборе меньшего числа фотографий их изображения будут иметь больший размер, как показано на рис. 21.10. Рис. 21.10. Процедура UserForm_Initialize определяет размер рисунков, исходя из их общего числа В следующих разделах рассматриваются приемы программирования, деY лающие возможным добавление на форму элементов управления во время выполнения программного кода. Изменение размеров формы во время выполнения программного кода Приведенный ниже код использует свойства формы Height и Width для изменения ее размеров во время выполнения программного кода. ' Изменить размер формы. Me.Height = Int(0.98 * ActiveWindow.Height) Me.Width = Int(0.98 * ActiveWindow.Width) Добавление элемента управления на форму во время выполнения программного кода Ниже приведен пример обращения к свойству элемента управления, доY бавленного на форму во время ее проектирования. Me.cbSave.Left = 100 Пользовательские формы — профессиональный подход Глава 21 535 Чтобы обратиться к свойству элемента управления, добавленного на форму во время выполнения программного кода, необходимо использовать коллекY цию Controls. Также рекомендуется создать переменную, хранящую имя элемента управления. Для добавления элемента управления на форму испольY зуется метод Add коллекции Controls. Параметр bstrProgId метода Add определяет тип элемента управления. Приведенный ниже код добавляет на форму надпись. Переменная PicCount используется для присвоения элементу управления уникального имеY ни. Свойства надписи Top и Left определяют ее положение на форме, а свойства Height и Width — размер надписи. LC = "LabelA" & PicCount Me.Controls.Add bstrProgId:="forms.label.1", Name:=LC, Visible:=True Me.Controls(LC).Top = 25 Me.Controls(LC).Left = 50 Me.Controls(LC).Height = 18 Me.Controls(LC).Width = 60 Me.Controls(LC).Caption = Cell.Value Определение размера и положения элемента управления на форме во время выполнения программного кода Определение размера (свойства Height и Width) и положения (Top и Left) элемента управления на форме осуществляется с учетом высоты и ширины формы, а также общего числа помещенных на форму элементов управления. Ограничения, связанные с добавлением элементов управления на форму во время выполнения программного кода Excel не сможет отобразить динамическую подсказку при обращении к элементам управления, добавляемым на форму во время выполнения проY граммного кода. К примеру, при наборе выражения Me.cbClose. Excel выY водит на экран подсказку, содержащую доступные методы, свойства и собыY тия соответствующего объекта. С другой стороны, при наборе выражения Me.Controls(LC). тип элемента управления заранее неизвестен. В связи с этим, в частности, следует помнить о том, что текст надписи задается свойстY вом Caption, а не свойством Value. Типы элементов управления Как уже упоминалось, тип добавляемого на форму элемента управления определяется параметром bstrProgId метода Controls.Add. Возможные значения этого параметра перечислены в табл. 21.1. 536 Часть III Удивительные возможности Visual Basic for Applications Таблица 21.1. Типы элементов управления Элемент управления Значение параметра bstrProgId Флажок (CheckBox) Forms.CheckBox.1 Комбинированный список (ComboBox) Forms.ComboBox.1 Кнопка (CommandButton) Forms.CommandButton.1 Панель (Frame) Forms.Frame.1 Изображение (Image) Forms.Image.1 Надпись (Label) Forms.Label.1 Список (ListBox) Forms.ListBox.1 Вкладка (MultiPage) Forms.MultiPage.1 Переключатель (OptionButton) Forms.OptionButton.1 Полоса прокрутки (ScrollBar) Forms.ScrollBar.1 Счетчик (SpinButton) Forms.SpinButton.1 Набор вкладок (TabStrip) Forms.TabStrip.1 Поле ввода (TextBox) Forms.TextBox.1 Выключатель (ToggleButton) Forms.ToggleButton.1 Добавление изображения на форму во время выполнения программного кода Добавление изображения на форму во время выполнения программного кода связано с определенными трудностями, поскольку ни ориентация изоY бражения YYYY альбомная или портретная, YYYY ни его размер заранее неизвестны. Решение этой задачи рекомендуется разбить на три этапа. Загрузите изобраY жение в натуральную величину, установив значение параметра AutoSize равным True, как показано ниже: TC = "Image" & PicCount Me.Controls.Add bstrProgId:="forms.image.1", Name:=TC, Visible:=True Me.Controls(TC).Top = LastTop Me.Controls(TC).Left = LastLeft Me.Controls(TC).AutoSize = True On Error Resume Next Me.Controls(TC).Picture = LoadPicture(fname) On Error GoTo 0 Загрузив изображение, определите его размер и ориентацию с помощью свойств Height и Width: ' Определить размер изображения. Wid = Me.Controls(TC).Width Ht = Me.Controls(TC).Height WidRedux = CellWid / Wid HtRedux = CellHt / Ht Пользовательские формы — профессиональный подход Глава 21 537 If WidRedux < HtRedux Then Redux = WidRedux Else Redux = HtRedux End If NewHt = Int(Ht * Redux) NewWid = Int(Wid * Redux) Определив исходный размер изображения, установите значение свойства AutoSize равным False и задайте требуемые значения свойств Height и Width. ' Изменить размер изображения. Me.Controls(TC).AutoSize = False Me.Controls(TC).Height = NewHt Me.Controls(TC).Width = NewWid Me.Controls(TC).PictureSizeMode = fmPictureSizeModeStretch Результирующий код Ниже приведен полный текст процедуры UserForm_Initialize, доY бавляющей элементы управления на форму во время выполнения проY граммного кода. Private Sub UserForm_Initialize() ' Эта процедура помещает на форму фотографии, имена ' файлов которых выбираются с помощью рабочего листа Excel. PicPath = ThisWorkbook.Path & Application.PathSeparator ' Изменить размер формы. Me.Height = Int(0.98 * ActiveWindow.Height) Me.Width = Int(0.98 * ActiveWindow.Width) ' Определить число выбранных ячеек. CellCount = Selection.Cells.Count ReDim Preserve Pics(1 To CellCount) ' Определить размер пользовательской формы. TempHt = Me.Height TempWid = Me.Width ' Определить число столбцов и строк с изображениями. NumCol = Int(0.99 + Sqr(CellCount)) NumRow = Int(0.99 + CellCount / NumCol) ' Зарезервировать 2 точки слева и ' справа от каждого изображения. CellWid = Application.WorksheetFunction.Max(Int(TempWid _ / NumCol) - 4, 1) ' Зарезервировать 33 точки под каждым ' изображением для размещения надписи. CellHt = Application.WorksheetFunction.Max(Int(TempHt _ / NumRow) - 33, 1) ' Переменная-счетчик. PicCount = 0 538 Часть III ' ' ' ' Удивительные возможности Visual Basic for Applications LastTop = 2 MaxBottom = 1 Создать строку изображений. For X = 1 To NumRow LastLeft = 3 Создать столбец изображений. For Y = 1 To NumCol PicCount = PicCount + 1 If PicCount > CellCount Then Изменить размер формы, чтобы вместить все изображения. Me.Height = MaxBottom + 100 Me.cbClose.Top = MaxBottom + 25 Me.cbClose.Left = Me.Width - 50 Repaint Exit Sub End If ThisStyle = Selection.Cells(PicCount).Value ThisDesc = Selection.Cells(PicCount).Offset(0, 1).Value fname = PicPath & ThisStyle & ".jpg " TC = "Image" & PicCount Me.Controls.Add bstrProgId:="forms.image.1", _ Name:=TC, Visible:=True Me.Controls(TC).Top = LastTop Me.Controls(TC).Left = LastLeft Me.Controls(TC).AutoSize = True On Error Resume Next Me.Controls(TC).Picture = LoadPicture(fname) On Error GoTo 0 ' Определить размер изображения. Wid = Me.Controls(TC).Width Ht = Me.Controls(TC).Height WidRedux = CellWid / Wid HtRedux = CellHt / Ht If WidRedux < HtRedux Then Redux = WidRedux Else Redux = HtRedux End If NewHt = Int(Ht * Redux) NewWid = Int(Wid * Redux) ' Изменить размер изображения. Me.Controls(TC).AutoSize = False Me.Controls(TC).Height = NewHt Me.Controls(TC).Width = NewWid Me.Controls(TC).PictureSizeMode = _ fmPictureSizeModeStretch Me.Controls(TC).ControlTipText = ThisStyle & ".jpg" Пользовательские формы — профессиональный подход Глава 21 539 Me.Controls(TC).Tag = fname ' Определить координаты нижнего правого угла изображения. ThisRight = Me.Controls(TC).Left + _ Me.Controls(TC).Width ThisBottom = Me.Controls(TC).Top + _ Me.Controls(TC).Height If ThisBottom > MaxBottom Then MaxBottom = ThisBottom ' Добавить подрисуночную надпись. LC = "LabelA" & PicCount Me.Controls.Add bstrProgId:="forms.label.1", _ Name:=LC, Visible:=True Me.Controls(LC).Top = ThisBottom + 1 Me.Controls(LC).Left = LastLeft Me.Controls(LC).Height = 18 Me.Controls(LC).Width = CellWid Me.Controls(LC).Caption = ThisDesc ' Эта строка позволяет увеличить ' изображение при щелчке на нем кнопкой мыши. Set Pics(PicCount).PictureGroup = Me.Controls(TC) ' Определить координаты следующего изображения. LastLeft = LastLeft + CellWid + 4 ' Конец строки. Next Y LastTop = MaxBottom + 21 + 16 Next X Me.Height = MaxBottom + 100 Me.cbClose.Top = MaxBottom + 25 Me.cbClose.Left = Me.Width - 50 Repaint End Sub Использование полосы прокрутки для выбора значений В главе 9, “Введение в пользовательские формы”, описывалось использоY вание счетчика для выбора даты. Недостаток счетчика состоит в том, что единственный способ изменения его значения заключается в щелчке на одной из клавиш счетчика. Альтернативный подход к выбору значения состоит в исY пользовании полосы прокрутки. Помимо щелчка на клавишах полосы проY крутки, ее значение можно выбрать путем перемещения ползунка. Пользовательская форма, показанная на рис. 21.11 и 21.12, содержит надY пись Label1 и полосу прокрутки ScrollBar1. 540 Часть III Удивительные возможности Visual Basic for Applications Рис. 21.11. Полоса прокрутки может применяться в качестве альтернативы счетчику Рис. 21.12. Преимущество полосы про& крутки заключается в возможности бы& строго изменения ее значения с помо& щью ползунка Процедура UserForm_Initialize задает начальное, минимальное и максимальное значение полосы прокрутки, а также текст надписи. Private Sub UserForm_Initialize() Me.ScrollBar1.Min = 0 Me.ScrollBar1.Max = 100 Me.ScrollBar1.Value = Range("A1").Value Me.Label1.Caption = Me.ScrollBar1.Value End Sub Обработчик события ScrollBar1_Change выполняется при щелчке на одной из кнопок полосы прокрутки. Private Sub ScrollBar1_Change() ' Событие ScrollBar1_Change срабатывает ' при щелчке на одной из кнопок полосы прокрутки. Me.Label1.Caption = Me.ScrollBar1.Value End Sub Обработчик события ScrollBar1_Scroll выполняется при щелчке на ползунке полосы прокрутки. Private Sub ScrollBar1_Scroll() ' Событие ScrollBar1_Scroll срабатывает ' при щелчке на ползунке полосы прокрутки. Me.Label1.Caption = Me.ScrollBar1.Value End Sub Обработчик события CommandButton1_Click выполняется при щелчке на кнопке OK. Следующий код копирует значение полосы прокрутки в ячейку A1 рабочего листа Excel и закрывает пользовательскую форму. Private Sub CommandButton1_Click() Range("A1").Value = Me.ScrollBar1.Value Unload Me End Sub Пользовательские формы — профессиональный подход Глава 21 541 Добавление подсказки к элементу управления Использование сочетаний клавиш Сочетания клавиш позволяют инициировать выполнение действий, наY пример, нажатие клавиши или установку флажка на форме. Как правило, соY четание клавиш определяется подчеркнутой буквой в названии кнопки или тексте надписи. Чтобы назначить сочетание клавиш для элемента управления пользоваY тельской формы, задайте значение свойства Accelerator. Действие, связанY ное с этим элементом управления, будет выполняться при нажатии комбинаY ции клавиш <Alt+значение свойства Accelerator>. Как показано на рис. 21.13, для установки/снятия флажка VHS можно применять сочетание клавиш <Alt+H>. Рис. 21.13. Сочетания клавиш используются для вы& полнения различных действий, связанных с элемен& тами управления формы Подсказка элемента управления При подведении указателя мыши к кнопке панели инструментов Excel на экране отображается подсказка, описывающая предназначение этой кнопки. Аналогичную функциональность можно реализовать для любого элемента управления формы путем установки значения свойства ControlTipText. На рис. 21.14 показана подсказка, отображающаяся на экране при подведении указателя мыши к переключателю Комедия. Порядок переноса фокуса Все пользовательские формы поддерживают перенос фокуса с одного элеY мента управления на другой при нажатии клавиши <Tab>. Порядок переноса фокуса определяется свойствами элемента управления TabStop и TabIndex. 542 Часть III Удивительные возможности Visual Basic for Applications Рис. 21.14. Подсказки упрощают работу с пользова& тельской формой Булево свойство TabStop определяет саму возможность переноса фокуса на элемент управления при нажатии клавиши <Tab>, а свойство TabIndex — порядковый номер элемента управления в группе (с отсчетом от нуля). Для создания группы элементов управления можно использовать панель. Два элеY мента управления в группе не могут иметь одинаковое значение свойства TabIndex. После переноса фокуса на флажок или переключатель значения последних можно устанавливать с помощью клавиши пробела (рис. 21.15). Рис. 21.15. Значения элементов управления этой фор& мы можно устанавливать с помощью клавиши <Tab> и клавиши пробела Изменение цвета фона активного элемента управления Изменение цвета фона активного элемента управления способно упростить взаимодействие пользователя с формой. Рассмотрим пример изменения цвета фона поля ввода и комбинированного списка, как показано на рис. 21.16. Пользовательские формы — профессиональный подход Глава 21 543 Рис. 21.16. Изменение цвета фо& на активного элемента управле& ния способно упростить взаи& модействие пользователя с формой Разместите следующий код в модуле класса clsCtlColor. Public Event GetFocus() Public Event LostFocus(ByVal strCtrl As String) Private strPreCtr As String Public Sub CheckActiveCtrl(objForm As MSForms.UserForm) With objForm If TypeName(.ActiveControl) = "ComboBox" Or _ TypeName(.ActiveControl) = "TextBox" Then strPreCtr = .ActiveControl.Name On Error GoTo Terminate Do DoEvents If .ActiveControl.Name <> strPreCtr Then If TypeName(.ActiveControl) = "ComboBox" Or _ TypeName(.ActiveControl) = "TextBox" Then RaiseEvent LostFocus(strPreCtr) strPreCtr = .ActiveControl.Name RaiseEvent GetFocus End If End If Loop End If End With Terminate: End Sub Ниже приведен код, который необходимо поместить в модуль формы. Private WithEvents objForm As clsCtlColor Private Sub UserForm_Initialize() Set objForm = New clsCtlColor End Sub Процедура UserForm_Activate изменяет цвет фона (свойство BackColor) активного элемента управления при отображении формы на экране. Private Sub UserForm_Activate() If TypeName(ActiveControl) = "ComboBox" Or _ TypeName(ActiveControl) = "TextBox" Then ActiveControl.BackColor = &HC0E0FF 544 Часть III Удивительные возможности Visual Basic for Applications End If objForm.CheckActiveCtrl Me End Sub Обработчик события objForm_GetFocus изменяет цвет фона элемента управления, на который был перенесен фокус. Private Sub objForm_GetFocus() ActiveControl.BackColor = &HC0E0FF End Sub Обработчик события objForm_LostFocus изменяет цвет фона элемента управления, с которого был перенесен фокус. Private Sub objForm_LostFocus(ByVal strCtrl As String) Controls(strCtrl).BackColor = &HFFFFFF End Sub Процедура UserForm_QueryClose удаляет объект objForm из памяти при закрытии формы. Private Sub UserForm_QueryClose(Cancel As Integer, _ CloseMode As Integer) Set objForm = Nothing End Sub Практикум Список с несколькими столбцами Рассмотрим следующую задачу. На рабочем листе Excel содержится информация о магазинах, включающая их названия и коды. Необходимо создать форму, позволяю& щую выбрать магазин по его названию и возвращающую в качестве результата код магазина. Несмотря на то, что подобная задача может быть решена с помощью функ& ций ВПР (VLOOKUP) и ПОИСКПОЗ (MATCH), рассмотрим альтернативный метод. Элемент управления ListBox (список) может содержать несколько столбцов, лишь часть из которых будет видна пользователю. К тому же список позволяет выбрать столбец, значение которого будет возвращено в качестве результата. Разместите на форме список и установите значение его свойства ColumnCount равным 2. Установите значение свойства RowSource равным Магазины, где Магазины — это имя диапазона ячеек, включающего список кодов (первый стол& бец) и названий (второй столбец) магазинов. Чтобы скрыть первый столбец, уста& новите значение свойства ColumnWidth равным 0 pt;100 pt, как показано на рис. 21.17. Теперь при отображении формы на экране пользователь увидит вполне привыч& ный список с одним столбцом. Чтобы возвратить значение первого столбца (столбца кодов магазинов), установите значение свойства списка BoundColumn равным 1. Это можно сделать с помощью окна свойств списка или с помощью программного кода, как показано ниже: Private Sub lb_StoreName_Click() lb_StoreName.BoundColumn = 1 lbl_StoreNum.Caption = lb_StoreName End Sub Пользовательские формы — профессиональный подход Глава 21 545 Рис. 21.17. Свойство списка ColumnWidth позволяет скрыть столбцы, не предназначенные для отображения на экране На рис. 21.18 показан результат решения поставленной задачи с помощью списка с несколькими столбцами. Рис. 21.18. Список с несколькими столбцами позволяет отображать на экране один столбец, а возвращать в качестве результата значение друго& го столбца Использование эффекта прозрачности формы Как показано на рис. 21.19, эффект прозрачности формы позволяет видеть содержимое расположенного под ней рабочего листа Excel. Разместите следующий код в начале модуля формы: Private Declare Function GetActiveWindow Lib "USER32" () As Long Private Declare Function SetWindowLong Lib "USER32" Alias _ "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long Private Declare Function GetWindowLong Lib "USER32" Alias _ "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) _ As Long Private Declare Function SetLayeredWindowAttributes Lib "USER32" _ (ByVal hWnd As Long, ByVal crKey As Integer, _ ByVal bAlpha As Integer, ByVal dwFlags As Long) As Long 546 Часть III Private Private Private Private Удивительные возможности Visual Basic for Applications Const Const Const Const WS_EX_LAYERED = &H80000 LWA_COLORKEY = &H1 LWA_ALPHA = &H2 GWL_EXSTYLE = &HFFEC Рис. 21.19. Полупрозрачная форма Обработчик события UserForm_Activate вызывает функции Windows API, необходимые для достижения эффекта прозрачности. Private Sub UserForm_Activate() Dim nIndex As Long hWnd = GetActiveWindow nIndex = GetWindowLong(hWnd, GWL_EXSTYLE) SetWindowLong hWnd, GWL_EXSTYLE, nIndex Or WS_EX_LAYERED ' Вызов этой функции позволяет достичь эффекта прозрачности. SetLayeredWindowAttributes hWnd, 0, (255 * (100 - rt)) / _ 100, LWA_ALPHA End Sub Следующий шаг Следующая глава посвящена основам использования функций интерфейса прикладного программирования (API) Windows. Ãëàâà 22 Èíòåðôåéñ ïðèêëàäíîãî ïðîãðàììèðîâàíèÿ (API) Windows Знакомство с Windows API Несмотря на разнообразие встроY енных функций Excel VBA, сущестY вуют задачи, которые можно выполY нить только с помощью средств инY терфейса прикладного программироY вания (API) Windows. Заглянув в папку \Windows\ System32 (или \Winnt\System32) на системном диске компьютера, можно увидеть множество файлов с расширением .dll. Эти файлы наY зываются динамически подключаемы* ми библиотеками, содержащими проY цедуры и функции, доступные друY гим программам (например, Excel). Следует помнить, что средства WinY dows API доступны только на компьюY терах, работающих под управлением операционной системы семейства Microsoft Windows. В этой главе рассматриваются осY новы использования объявлений Windows API, а также приводится неY сколько полезных примеров. 22 Знакомство с Windows API.........547 Объявления Windows API.......... 548 Использование объявлений Windows API ................................. 548 Примеры полезных объявлений Windows API.......... 549 Дополнительные источники объявлений Windows API.......... 559 Следующий шаг........................... 559 548 Часть III Удивительные возможности Visual Basic for Applications Объявления Windows API Рассмотрим пример объявления Windows API. Private Declare Function GetUserName Lib "advapi32.dll" Alias _ "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long Существует два типа объявлений Windows API — функции, возвращающие информацию, и процедуры, выполняющие какиеYлибо действия. Разберем структуру приведенного выше объявления Windows API. Private. Ключевое слово, определяющее переменную, функцию или процедуру, использующуюся в пределах одного модуля. Чтобы объяY вить общедоступную переменную, функцию или процедуру, примеY няйте ключевое слово Public. Внимание Объявления Windows API, размещенные в стандартном модуле, могут иметь об& ласть видимости Public или Private. Объявления Windows API, размещенные в модуле класса, должны иметь область видимости Private. Declare Function GetUserName. Объявление функции GetUserName. Имя функции может быть произвольным. Lib "advapi32.dll". Функция API находится в библиотеке advapi32.dll. Alias "GetUserNameA". Псевдоним функции в библиотеке DLL. Псевдоним чувствителен к регистру и должен быть набран в том виде, в котором он существует в библиотеке DLL. Обычно каждая функция API имеет два псевдонима. Псевдоним функции API, использующей набор символов ANSI, заканчивается буквой A, а псевдоним функции API, использующей набор символов Unicode, — буквой W. ByVal lpBuffer As String, nSize As Long. Функция API приY нимает два параметра YYYY lpBuffer типа String и nSize типа Long. Использование объявлений Windows API Использование объявления Windows API ничем не отличается от вызова функции или процедуры VBA. Ниже приведен пример использования объявY ления Windows API GetUserName. Private Function UserName() As String Dim sName As String * 256 Dim cChars As Long cChars = 256 If GetUserName(sName, cChars) Then UserName = Left$(sName, cChars - 1) End If Интерфейс прикладного программирования (API) Windows Глава 22 549 End Function Sub ProgramRights() Dim NameofUser As String NameofUser = UserName Select Case NameofUser Case Is = "Администратор" MsgBox "Вы имеете права администратора на этом компьютере" Case Else MsgBox "Вы имеете ограниченные права на этом компьютере" End Select End Sub Макрос ProgramRights позволяет узнать, имеет ли текущий пользоваY тель права администратора компьютера (рис. 22.1). Примеры полезных объявлений Windows API В этом разделе рассматриваются полезные объявления Windows API вместе с краткими описаниями и примерами использования. Определение имени компьютера Объявление Windows API GetComputerName возвращает имя компьютера. Private Declare Function GetComputerName Lib "kernel32" _ Alias "GetComputerNameA" (ByVal lpBuffer As String, _ ByRef nSize As Long) As Long Private Function ComputerName() As String Dim stBuff As String * 255, lAPIResult As Long Dim lBuffLen As Long lBuffLen = 255 lAPIResult = GetComputerName(stBuff, lBuffLen) If lBuffLen > 0 Then ComputerName = Left(stBuff, lBuffLen) End Function Sub ComputerCheck() Dim CompName As String CompName = ComputerName MsgBox Prompt:=CompName, Buttons:=vbOKOnly, _ Title:="Имя компьютера" End Sub Макрос ComputerCheck выводит на экран окно сообщения, содержащего имя компьютера. Результат выполнения макроса ComputerCheck показан на рис. 22.2. 550 Часть III Удивительные возможности Visual Basic for Applications Рис. 22.1. Объявление Windows API GetUserName позволяет узнать имя пользователя компьютера и опреде& лить, имеет ли пользователь права администратора Рис. 22.2. Объявление Windows API GetComputerName позволяет узнать имя компьютера Проверка возможности доступа к файлу Объявления Windows API lOpen и lClose позволяют узнать, открыт или закрыт заданный файл. Private Declare Function lOpen Lib "kernel32" Alias "_lopen" _ (ByVal lpPathName As String, ByVal iReadWrite As Long) As Long Private Declare Function lClose Lib "kernel32" _ Alias "_lclose" (ByVal hFile As Long) As Long Private Function FileIsOpen(strFullPath_FileName _ As String) As Boolean Dim hdlFile As Long Dim lastErr As Long hdlFile = -1 hdlFile = lOpen(strFullPath_FileName, OF_SHARE_EXCLUSIVE) If hdlFile = -1 Then lastErr = Err.LastDllError Else lClose hdlFile End If FileIsOpen = (hdlFile = -1) And (lastErr = 32) End Function Sub CheckFileOpen() Dim txtFile As String txtFile = GetFileName("c:\") If txtFile = "" Then Exit Sub If FileIsOpen(txtFile) Then MsgBox "Файл занят (открыт)" Else MsgBox "Файл не занят" End If End Sub Макрос CheckFileOpen позволяет выбрать файл и узнать, открыт он или закрыт (рис. 22.3). Определение разрешения экрана Объявление Windows API DisplaySize позволяет определить разрешение экрана компьютера. Интерфейс прикладного программирования (API) Windows Глава 22 551 Private Declare Function DisplaySize Lib "user32" Alias _ "GetSystemMetrics" (ByVal nIndex As Long) As Long Private Function VideoRes() As String Dim vidWidth Dim vidHeight vidWidth = DisplaySize(SM_CXSCREEN) vidHeight = DisplaySize(SM_CYSCREEN) Select Case (vidWidth * vidHeight) Case 307200 VideoRes = "640 x 480" Case 480000 VideoRes = "800 x 600" Case 786432 VideoRes = "1024 x 768" Case Else VideoRes = "Другое разрешение" End Select End Function Sub CheckDisplayRes() Dim VideoInfo As String Dim Msg1 As String, Msg2 As String, Msg3 As String VideoInfo = VideoRes Msg1 = "Текущее разрешение экрана: " & VideoInfo & Chr(10) Msg2 = "Оптимальное разрешение экрана для этого приложения: _ 1024 x 768 " & Chr(10) Msg3 = "Пожалуйста, увеличьте разрешение экрана" Select Case VideoInfo Case Is = "640 x 480" MsgBox Msg1 & Msg2 & Msg3 Case Is = "800 x 600" MsgBox Msg1 & Msg2 & Msg3 Case Is = "1024 x 768" MsgBox Msg1 Case Else MsgBox Msg2 & Msg3 End Select End Sub Макрос CheckDisplayRes проверяет текущее разрешение экрана и при необходимости рекомендует его увеличить, как показано на рис. 22.4. Рис. 22.3. Объявления Windows API lOpen и lClose могут быть ис& пользованы для того, чтобы узнать, открыт ли заданный файл Рис. 22.4. Объявление Windows API DisplaySize позволяет определить разрешение экрана компьютера 552 Часть III Удивительные возможности Visual Basic for Applications Блокирование кнопки закрытия окна приложения В правом верхнем углу окна приложения находится кнопка, предназначенY ная для его закрытия (кнопка с изображением знака ‘‘×’’). Блокировать кнопY ку закрытия окна приложения можно с помощью объявлений Windows API FindWindow, GetSystemMenu и DeleteMenu. На заметку Не забудьте разблокировать кнопку закрытия окна приложения с помощью выра& жения XButtonEnabled True. Private Declare Function FindWindow Lib "user32" Alias _ "FindWindowA" (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function GetSystemMenu Lib "user32" _ (ByVal hwnd As Long, ByVal bRevert As Long) As Long Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu _ As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long Private Sub XButtonEnabled(ByVal bEnabled As Boolean) Dim hWndForm As Long Dim hMenu As Long hWndForm = FindWindow("XLMAIN", Application.Caption) hMenu = GetSystemMenu(hWndForm, bEnabled) DeleteMenu hMenu, SC_CLOSE, 0& End Sub Sub DisableExcelXButton() XButtonEnabled False MsgBox "Кнопка закрытия окна приложения заблокирована" End Sub Sub EnableExcelXButton() XButtonEnabled True MsgBox "Кнопка закрытия окна приложения разблокирована" End Sub Обратите внимание, что псевдоним функции DLL указан только в первом объявлении Windows API. На самом деле, псевдоним можно и не указывать, однако тогда имя объявляемой функции должно полностью совпадать с ее именем в библиотеке DLL. На рис. 22.5 показан результат выполнения макроса DisableExcelXButton. Блокирование кнопки закрытия окна формы По аналогии с предыдущим примером создадим код, блокирующий кнопку закрытия окна формы при инициализации последней. Private Declare Function FindWindow Lib "user32" Alias _ "FindWindowA" (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function GetSystemMenu Lib "user32" _ Интерфейс прикладного программирования (API) Windows Глава 22 553 (ByVal hwnd As Long, ByVal bRevert As Long) As Long Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu _ As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long Private Const SC_CLOSE As Long = &HF060 Private Sub UserForm_Initialize() Dim hWndForm As Long Dim hMenu As Long hWndForm = FindWindow("ThunderDFrame", Me.Caption) hMenu = GetSystemMenu(hWndForm, 0) DeleteMenu hMenu, SC_CLOSE, 0& End Sub Результат выполнения обработчика события UserForm_Initialize поY казан на рис. 22.6. Пользователь может закрыть форму только с помощью щелчка на кнопке Выход. Рис. 22.5. Для блокирования кнопки за& крытия окна приложения используются объявления Windows API FindWindow, GetSystemMenu и DeleteMenu Рис. 22.6. Единственный спо& соб закрыть форму заключа& ется в щелчке на кнопке Выход Часы Функция Excel VBA ТДАТА (NOW) возвращает текущие дату и время, однако ее возможностей оказывается недостаточно для реализации простых часов. Данную функциональность можно получить с помощью объявлений Windows API SetTimer, KillTimer и FindWindow, как показано ниже. Private Declare Function SetTimer Lib "user32" _ (ByVal hwnd As Long, ByVal nIDEvent As Long, _ ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long Private Declare Function KillTimer Lib "user32" _ (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long Private Declare Function FindWindow Lib "user32" _ Alias "FindWindowA" (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private lngTimerID Sub StartTimer() ' Остановить текущий таймер. StopTimer 554 Часть III Удивительные возможности Visual Basic for Applications lngTimerID = SetTimer(0, 1, 10, AddressOf RunTimer) End Sub Sub StopTimer() Dim lRet As Long, lngTID As Long If IsEmpty(lngTimerID) Then Exit Sub lngTID = lngTimerID lRet = KillTimer(0, lngTID) lngTimerID = Empty End Sub Private Sub RunTimer(ByVal hwnd As Long, _ ByVal uint1 As Long, ByVal nEventId As Long, _ ByVal dwParam As Long) On Error Resume Next Sheet1.Range("A1").Value = Format(Now, "hh:mm:ss") End Sub Макрос StartTimer размещает часы в ячейке A1, как показано на рис. 22.7. Рис. 22.7. Часы, созданные с помощью объявлений Windows API SetTimer, KillTimer и FindWindow Создание гиперссылок Объявление Windows API ShellExecute позволяет разместить на форме гиперссылки YYYY надписи с адресом электронной почты или адресом WebY сайта, щелчок на которых приводит к запуску соответствующего приложения. Private Declare Function ShellExecute Lib "shell32.dll" _ Alias "ShellExecuteA" (ByVal hwnd As Long, _ ByVal lpOperation As String, ByVal lpFile As String, _ ByVal lpParameters As String, ByVal lpDirectory As String, _ ByVal nShowCmd As Long) As Long Const SWNormal = 1 Private Sub lbl_Email_Click() Dim lngRow As Long ShellExecute 0&, "open", "mailto:" & lbl_Email.Caption, _ vbNullString, vbNullString, SWNormal End Sub Private Sub lbl_Website_Click() Dim lngRow As Long ShellExecute 0&, "open", lbl_Website.Caption, _ vbNullString, vbNullString, SWNormal End Sub Интерфейс прикладного программирования (API) Windows Глава 22 555 Щелчок на надписи [email protected] приводит к открытию программы, предназначенной для работы с электронной почтой, а щелчок на надписи www.mrexcel.com — к запуску обозревателя Internet, как показано на рис. 22.8. Рис. 22.8. Объявление Windows API ShellExecute позволяет разместить на фор& ме гиперссылку надпись, щелчок на которой приводит к запуску внешнего при& ложения Воспроизведение звуковых файлов Желаете предупредить пользователя об опасности или поздравить его с усY пешным выполнением задачи посредством воспроизведения звукового файла? Воспользуйтесь объявлением Windows API PlayWavSound, как показано ниже. Private Declare Function PlayWavSound Lib "winmm.dll" _ Alias "sndPlaySoundA" (ByVal LpszSoundName As String, _ ByVal uFlags As Long) As Long Private Sub CommandButton2_Click() SoundName = Application.GetOpenFilename("Формат WAV _ (*.wav), *.wav", , "Выберите файл формата WAV", "Воспроизвести") If SoundName = False Then Exit Sub PlayWavSound SoundName, 0 End Sub Создание диалогового окна выбора файла Рассмотрим код, использующий объявления Windows API для создания диалогового окна выбора файла. 556 Часть III Удивительные возможности Visual Basic for Applications Type tagOPENFILENAME lStructSize As Long hwndOwner As Long hInstance As Long strFilter As String strCustomFilter As String nMaxCustFilter As Long nFilterIndex As Long strFile As String nMaxFile As Long strFileTitle As String nMaxFileTitle As Long strInitialDir As String strTitle As String Flags As Long nFileOffset As Integer nFileExtension As Integer strDefExt As String lCustData As Long lpfnHook As Long lpTemplateName As String End Type Declare Function aht_apiGetOpenFileName Lib "comdlg32.dll" _ Alias "GetOpenFileNameA" (OFN As tagOPENFILENAME) As Boolean Declare Function aht_apiGetSaveFileName Lib "comdlg32.dll" _ Alias "GetSaveFileNameA" (OFN As tagOPENFILENAME) As Boolean Declare Function CommDlgExtendedError Lib "comdlg32.dll" () As Long Global Global Global Global Global Global Global Global Global Global Global Global Global Global Global Global Global Global Global Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const ahtOFN_READONLY = &H1 ahtOFN_OVERWRITEPROMPT = &H2 ahtOFN_HIDEREADONLY = &H4 ahtOFN_NOCHANGEDIR = &H8 ahtOFN_SHOWHELP = &H10 ahtOFN_NOVALIDATE = &H100 ahtOFN_ALLOWMULTISELECT = &H200 ahtOFN_EXTENSIONDIFFERENT = &H400 ahtOFN_PATHMUSTEXIST = &H800 ahtOFN_FILEMUSTEXIST = &H1000 ahtOFN_CREATEPROMPT = &H2000 ahtOFN_SHAREAWARE = &H4000 ahtOFN_NOREADONLYRETURN = &H8000 ahtOFN_NOTESTFILECREATE = &H10000 ahtOFN_NONETWORKBUTTON = &H20000 ahtOFN_NOLONGNAMES = &H40000 ahtOFN_EXPLORER = &H80000 ahtOFN_NODEREFERENCELINKS = &H100000 ahtOFN_LONGNAMES = &H200000 Function ahtCommonFileOpenSave( _ Optional ByRef Flags As Variant, _ Optional ByVal InitialDir As Variant, _ Optional ByVal Filter As Variant, _ Optional ByVal FilterIndex As Variant, _ Optional ByVal DefaultExt As Variant, _ Optional ByVal FileName As Variant, _ Optional ByVal DialogTitle As Variant, _ Optional ByVal hwnd As Variant, _ Optional ByVal OpenFile As Variant) As Variant Интерфейс прикладного программирования (API) Windows ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' Эта функция вызывает стандартное диалоговое окно выбора файла. Все перечисленные выше параметры являются необязательными. Входящие параметры: - Flags. Одна или несколько ahtOFN-констант, объединенных оператором OR. - InitialDir. Начальная папка. - Filter. Набор фильтров отбора файлов (для создания фильтров используется функция ahtAddFilterItem). - FilterIndex. Номер используемого фильтра. По умолчанию 1. - DefaultExt. Расширение файла, используемое по умолчанию. Применяется при сохранении файла. - FileName. Значение по умолчанию поля ввода имени файла. - DialogTitle. Заголовок диалогового окна. - hWnd. Дескриптор родительского окна. - OpenFile. Булево значение: True - открыть файл, False - сохранить файл. Возвращаемый результат: - Null или имя выбранного файла. Dim OFN As tagOPENFILENAME Dim strFileName As String Dim strFileTitle As String Dim fResult As Boolean ' Задать параметры диалогового окна. If IsMissing(InitialDir) Then InitialDir = CurDir If IsMissing(Filter) Then Filter = "" If IsMissing(FilterIndex) Then FilterIndex = 1 If IsMissing(Flags) Then Flags = 0& If IsMissing(DefaultExt) Then DefaultExt = "" If IsMissing(DialogTitle) Then DialogTitle = "" If IsMissing(OpenFile) Then OpenFile = True ' Создать строковые переменные для хранения ' возвращаемых значений. strFileName = Left(FileName & String(256, 0), 256) strFileTitle = String(256, 0) ' Определить значение полей структуры OFN. With OFN .lStructSize = Len(OFN) .strFilter = Filter .nFilterIndex = FilterIndex .strFile = strFileName .nMaxFile = Len(strFileName) .strFileTitle = strFileTitle .nMaxFileTitle = Len(strFileTitle) .strTitle = DialogTitle .Flags = Flags .strDefExt = DefaultExt .strInitialDir = InitialDir .hInstance = 0 .lpfnHook = 0 .strCustomFilter = String(255, 0) .nMaxCustFilter = 255 End With Глава 22 557 558 Часть III Удивительные возможности Visual Basic for Applications ' Передать структуру OFN функции Windows API, ' отображающей диалоговое окно выбора файла. If OpenFile Then fResult = aht_apiGetOpenFileName(OFN) Else fResult = aht_apiGetSaveFileName(OFN) End If ' В результате вызова функции API значение поля strFile ' структуры OFN было изменено. Следующий код извлекает ' полученное значение поля strFile. If fResult Then ' Информация о выбранном файле хранится в поле Flags ' структуры OFN. Следующий код извлекает полученное ' значение этого поля для последующего анализа. If Not IsMissing(Flags) Then Flags = OFN.Flags ahtCommonFileOpenSave = TrimNull(OFN.strFile) Else ahtCommonFileOpenSave = vbNullString End If End Function Private Function ahtAddFilterItem(strDescription As String, _ strFilter As String, Optional varItem As Variant) As String ' Эта функция создает фильтр файлов. ' Фильтр файлов = описание фильтра файлов (например, ' "Базы данных ") + символ NULL + шаблон фильтра файлов ' (например, "*.mdb;*.mda ") + символ NULL. If IsMissing(varItem) Then varItem = "*.*" ahtAddFilterItem = strDescription & vbNullChar & strFilter & _ vbNullChar & varItem & vbNullChar End Function Private Function TrimNull(ByVal strItem As String) As String Dim intPos As Integer intPos = InStr(strItem, vbNullChar) If intPos > 0 Then TrimNull = Left(strItem, intPos - 1) Else TrimNull = strItem End If End Function Следующая функция отображает диалоговое окно выбора файла и возвраY щает имя последнего. Function GetFileName(strPath As String) Dim strFilter As String Dim lngFlags As Long strFilter = "*.xls" strFilter = ahtAddFilterItem("Файлы Excel (*.xls)", strFilter) GetFileName = ahtCommonFileOpenSave(InitialDir:=strPath, _ Filter:=strFilter, FilterIndex:=3, Flags:=lngFlags, _ DialogTitle:="Пожалуйста, выберите файл", FileName:="") End Function Создайте пользовательскую форму. Ниже приведен код, выполняющийся в результате щелчка на кнопке Выбрать файл, как показано на рис. 22.9. ОбраY Интерфейс прикладного программирования (API) Windows Глава 22 559 тите внимание, что функция GetFileName принимает в качестве параметра путь к папке, с которой будет начат обзор файловой системы. Private Sub CommandButton2_Click() Dim txtFile As String txtFile = GetFileName("C:\") If txtFile <> "" Then ListBox1.AddItem (txtFile) End If End Sub Рис. 22.9. Диалоговое окно, возвращающее имя выбранного файла Дополнительные источники объявлений Windows API Многие программисты не прочь поделиться опытом в создании и использоваY нии объявлений Windows API. В частности, на WebYсайте Ивана Ф. Моалы (Ivan F. Moala) The XcelFiles (www.xcelfiles.com) содержится множество примеров объявлений Windows API с соответствующими пояснениями (посетите WebY страницу по адресу: http://www.xcelfiles.com/APIIndex.html). Следующий шаг Следующая глава посвящена обработке ошибок. Глава 23 Îáðàáîòêà îøèáîê Сколько бы усилий ни было заY трачено на тестирование и отладку программного кода, рано или поздно он даст сбой. Единственная защита от непредвиденных ситуаций заклюY чается в их планировании. Отладка кода с помощью редактора VBA Обнаружив необработанную ошибY ку в программном коде, редактор VBA отображает диалоговое окно, пример которого показан на рис. 23.1. Щелкните на кнопке Debug (Отладка). Редактор VBA выделяет строку кода, выполнение которой привело к возникновению ошибки, желтым цветом, как показано на рис. 23.2. Рис. 23.1. Результат обнаружения необрабо& танной ошибки в незащищенном модуле 23 Отладка кода с помощью редактора VBA.............................. 561 Обработка ошибок с помощью выражения On Error GoTo ................................ 564 Универсальные обработчики ошибок ................. 566 Общение с заказчиками............ 569 “Отложенные” ошибки ............. 569 Несовершенство защиты проекта VBA ..................................572 Защита проекта VBA в различных версиях Excel............573 Совместимость различных версий Excel ...................................573 Следующий шаг............................574 562 Часть III Удивительные возможности Visual Basic for Applications Рис. 23.2. Чтобы узнать текущее значение переменной, подве& дите к ней указатель мыши — и вы получите дополнительную информацию о причине сбоя К сожалению, редактор VBA не слишком разборчив в том, что касается опY ределения типа ошибки. В частности, огромное число сбойных ситуаций классифицируются как ошибка времени выполнения 1004. Таким образом, единственный способ определения причины сбоя заключается в изучении строки, на которой было остановлено выполнение программного кода, и теY кущих значений переменных. Установив возможные причины возникновения ошибки, щелкните на кнопке Reset (Сброс), чтобы прекратить выполнение макроса (рис. 23.3). Рис. 23.3. Кнопка Reset напоминает кнопку Стоп, располо& женную на пульте дистанционного управления CD& или DVD&плейера Внимание Попытка выполнения макроса в режиме отладки другого макроса приводит к ото& бражению сообщения об ошибке, показанного на рис. 23.4. Если запуск макроса был инициирован с помощью интерфейса Excel, на экране появится окно редакто& ра VBA. Тем не менее, после щелчка на кнопке OK на экране вновь появится окно Excel, что весьма неудобно. Отладка кода пользовательской формы Иногда редактор VBA неверно определяет строку кода, выполнение котоY рой привело к возникновению ошибки. Рассмотрим следующую ситуацию. Предположим, что сбой произошел при выполнении кода формы, отображаеY мой на экране в результате выполнения некоторого макроса. После перехода в режим отладки редактор VBA выделяет желтым цветом строку вызова формы, что может ввести в заблуждение. Чтобы отыскать настоящий источник ошибY ки, выполните следующие действия. Обработка ошибок Глава 23 563 Рис. 23.4. Это сообщение выводится редактором VBA при попытке запуска макроса в режиме отладки другого макроса 1. В окне сообщения об ошибке щелкните на кнопке Debug (Отладка), как показано на рис. 23.5. Рис. 23.5. Щелкните на кнопке Debug 2. Редактор VBA выделит желтым цветом строку вызова пользовательской формы frmChoose.Show, как показано на рис. 23.6. Это неверное реY шение, поскольку сбой произошел при выполнении кода пользовательY ской формы. Рис. 23.6. Редактор VBA неверно определяет строку, выполнение которой привело к возникновению ошибки 564 Часть III Удивительные возможности Visual Basic for Applications 3. Нажмите клавишу <F8>, чтобы выполнить метод frmChoose.Show. Вместо выдачи сообщения об ошибке редактор VBA приступит к выY полнению кода процедуры инициализации формы UserForm_ Initialize. 4. Нажимайте клавишу <F8> до тех пор, пока редактор VBA вновь не отоY бразит сообщение об ошибке. Поиск проблемного участка кода может быть затруднен наличием длинного цикла, как показано на рис. 23.7. Рис. 23.7. Число нажатий клавиши <F8>, необходимое для прохождения цикла For...Next, прямо пропорционально числу его итераций Для прохождения первой итерации цикла For...Next достаточно трех нажатий клавиши <F8> (см. рис. 23.7). Каждая следующая итерация цикла требует нажатия клавиши <F8> два раза. В частности, чтобы добавить в спиY сок 25 элементов, клавишу <F8> придется нажать 51 раз. Другими словами, отладка кода пользовательской формы представляет соY бой весьма утомительное занятие. Запаситесь терпением и не забывайте слеY дить за выполняемой на следующем шаге строкой. Обработка ошибок с помощью выражения On Error GoTo Логика обработки ошибок в VBA заключается в передаче выполнения кода некоторому заранее подготовленному участку макроса. Чтобы создать код обработки ошибки, выполните следующие действия. 1. Разместите строку Exit Sub после основного кода макроса, чтобы предотвратить несанкционированное выполнение кода обработки ошибки. 2. Добавьте метку после строки Exit Sub, например, MyErrorHandler:. 3. Разместите после метки код обработки ошибки. Чтобы передать выполY нение кода строке макроса, следующей за строкой, которая привела к возникновению ошибки, воспользуйтесь выражением Resume Next. Обработка ошибок Глава 23 565 4. Разместите строку On Error GoTo MyErrorHandler перед строкой, выполнение которой может привести к возникновению ошибки. Обратите внимание, что на этот раз двоеточие после имени метки ставить не нужно. 5. Разместите строку On Error GoTo 0 после строки кода, выполнение которой может привести к возникновению ошибки. На самом деле, метки с именем 0 не существует. Выражение On Error GoTo 0 поY зволяет вернуться в обычный режим обработки ошибок. Рассмотрим пример создания кода обработки ошибки открытия файла. Sub HandleAnError() Dim MyFile As Variant ' Определить обработчик ошибок. On Error GoTo FileNotThere Workbooks.Open Filename:="C:\NotHere.xls" ' Вернуться в обычный режим обработки ошибок. On Error GoTo 0 MsgBox "Выполнение макроса завершено" ' Строка Exit Sub позволяет предотвратить ' несанкционированное выполнение обработчика ошибки. Exit Sub ' Создать метку. FileNotThere: MyPrompt = "При открытии файла произошла ошибка. Вероятно, " MyPrompt = MyPrompt & "файл не существует. Щелкните на кнопке " MyPrompt = MyPrompt & "OK, чтобы указать расположение файла, " MyPrompt = MyPrompt & "или на кнопке Cancel, чтобы завершить " MyPrompt = MyPrompt & "выполнение макроса" Ans = MsgBox(Prompt:=MyPrompt, Buttons:=vbOKCancel) If Ans = vbCancel Then Exit Sub ' Отобразить диалоговое окно выбора файла. MyFile = Application.GetOpenFilename ' Если пользователь ничего не выбрал, завершить выполнение макроса. If MyFile = False Then Exit Sub On Error GoTo 0 Workbooks.Open MyFile ' Передать выполнение кода строке макроса, следующей ' за строкой, которая привела к возникновению ошибки. Resume Next End Sub Использование нескольких обработчиков ошибок В коде макроса можно разместить несколько различных обработчиков ошибок. Единственным требованием при этом является наличие строки Resume Next или Exit Sub в конце кода каждого обработчика. 566 Часть III Удивительные возможности Visual Basic for Applications Универсальные обработчики ошибок Некоторые программисты отдают предпочтение универсальному обработY чику ошибок, позволяющему скрыть от пользователей сообщение об ошибке, генерируемое редактором VBA. Как правило, универсальный обработчик ошибок обращается к свойствам объекта Err, таким как номер ошибки и ее описание. Sub GenericHandler() On Error GoTo HandleAny Sheets(9).Select Exit Sub HandleAny: Msg = "Произошла ошибка номер " & Err.Number & ". Описание _ ошибки: " & Err.Description MsgBox Msg Exit Sub End Sub Игнорирование ошибок Некоторые ошибки можно безболезненно проигнорировать. Вспомним макрос создания WebYстраницы, рассматривавшийся в главе 14, ‘‘ВзаимодейY ствие с Internet’’. Перед созданием HTMLYфайла sampledirectory.html макрос WriteMembershipHTML удаляет существующую копию этого файла. Выражение Kill (Файл) возвращает ошибку, если файл с именем Файл не существует. Однако эта ошибка столь несущественна, что ее можно проигY норировать и продолжить выполнение макроса со следующей строки. Sub WriteMembershipHTML() ... MyFile = "sampledirectory.html" ThisFile = MyPath & Application.PathSeparator & MyFile ThisHostFile = MyFile ' Удалить существующую Web-страницу. On Error Resume Next Kill (ThisFile) On Error GoTo 0 ' Открыть файл для записи. Open ThisFile For Output As #1 ... End Sub Ни в коем случае не злоупотребляйте выражением On Error Resume Next. Также не забывайте размещать строку On Error GoTo 0 сразу же поY сле строки кода, выполнение которой может привести к возникновению ошибки. Обработка ошибок Глава 23 567 Попытка проигнорировать критическую ошибку приводит к завершению выполнения макроса. Если макрос А вызывает макрос Б и выполнение поY следнего завершается аварийно, выполнение кода продолжается со строки макроса А, следующей за строкой вызова макроса Б. Это чревато непредскаY зуемыми последствиями. Игнорирование ошибок задания параметров печати страницы Запишите макрос, задающий параметры печати страницы. Установка всего лишь одного значения в диалоговом окне Параметры страницы (Page Setup) приводит к созданию множества строк кода. Проблема заключается в том, что параметры печати страницы отличаются от принтера к принтеру. Например, при создании макроса на компьютере с принтером, поддерживающим цветY ную печать, средство записи макросов сгенерирует строку .BlackAndWhite = False. Выполнение этой строки на компьютере с принтером, поддерY живающим только черноYбелую печать, приведет к возникновению ошибки. Еще одной причиной сбоя может стать установка недопустимых значений паY раметров, например, параметра, определяющего качество печати. Если макY рос был создан на компьютере с принтером, поддерживающим разрешение 600 точек на дюйм, средство записи макросов сгенерирует строку .PrintQuality = 600. Выполнение этой строки на компьютере с принтеY ром, поддерживающим максимальное разрешение 300 точек на дюйм, привеY дет к сбою. В данной ситуации рекомендуется разместить выражение On Error Resume Next перед фрагментом кода, устанавливающим параметры печати страницы, а выражение On Error GoTo 0 — после него. On Error Resume Next With ActiveSheet.PageSetup .PrintTitleRows = "" .PrintTitleColumns = "" End With ActiveSheet.PageSetup.PrintArea = "$A$1:$L$27" With ActiveSheet.PageSetup .LeftHeader = "" .CenterHeader = "" .RightHeader = "" .LeftFooter = "" .CenterFooter = "" .RightFooter = "" .LeftMargin = Application.InchesToPoints(0.25) .RightMargin = Application.InchesToPoints(0.25) .TopMargin = Application.InchesToPoints(0.75) .BottomMargin = Application.InchesToPoints(0.5) .HeaderMargin = Application.InchesToPoints(0.5) .FooterMargin = Application.InchesToPoints(0.5) .PrintHeadings = False .PrintGridlines = False .PrintComments = xlPrintNoComments .PrintQuality = 300 .CenterHorizontally = False .CenterVertically = False 568 Часть III Удивительные возможности Visual Basic for Applications .Orientation = xlLandscape .Draft = False .Pauperize = xlPaperLetter .FirstPageNumber = xlAutomatic .Order = xlDownThenOver .BlackAndWhite = False .Zoom = False .FitToPagesWide = 1 .FitToPagesTall = False .PrintErrors = xlPrintErrorsDisplayed End With On Error GoTo 0 Игнорирование сообщений Excel Некоторые сообщения Excel выводятся на экран вне зависимости от текуY щего режима обработки ошибок. Например, при попытке удаления рабочего листа на экране появится сообщение В листах, выбранных для удаления, могут существовать данные. Чтобы удалить данные, нажмите кнопку "Удалить". (Data may exist in the sheet(s) selected for deleY tion. To permanently delete the data, click Delete.). Это смущает многих пользоY вателей, так как они полагают, что произошла какаяYлибо ошибка. Чтобы отY ключить вывод сообщений Excel, воспользуйтесь выражением Application.DisplayAlerts = False. Sub DeleteSheet() Application.DisplayAlerts = False Worksheets("Лист2").Delete Application.DisplayAlerts = True End Sub Извлечение пользы из ошибок Звучит весьма странно, однако даже ошибки могут приносить определенY ную пользу. В частности, ошибки помогают разрабатывать более быстрый и эффективный код. Рассмотрим задачу проверки наличия в активной рабочей книге листа с именем Данные. Ниже приведен код, решающий эту задачу без использоваY ния ошибок. Sub AvoidTheErrorLongerCode() DataFound = False For Each WS In ActiveWorkbook.Worksheets If WS.Name = "Данные" Then DataFound = True Exit For End If Next WS If Not DataFound Then Sheets.Add.Name = "Данные" End Sub Обработка ошибок Глава 23 569 Если активная рабочая книга содержит 128 листов, тело цикла For Each...Next будет выполнено 128 раз при условии, что листа с именем Данные в книге нет. Альтернативный подход заключается в попытке обращения к листу Данные. Проигнорируйте обработку ошибок с помощью выражения On Error Resume Next и проверьте номер ошибки Err.Number. Если номер ошибки отличен от нуля, листа Данные в рабочей книге нет. Sub ErrorOnPurposeShorter() On Error Resume Next x = Worksheets("Данные").Name If Not Err.Number = 0 Then Sheets.Add.Name = "Данные" On Error GoTo 0 End Sub Очевидно, что макрос ErrorOnPurposeShorter выполняется быстрее макроса AvoidTheErrorLongerCode. Иногда даже ошибки способны приY носить пользу. Общение с заказчиками Одним из наиболее важных аспектов общения с заказчиками является окаY зание поддержки в вопросах, возникающих при использовании разработанY ных для них макросов. Постарайтесь объяснить заказчикам разницу между сообщением об ошибY ке и обычным сообщением Excel. К сожалению, сообщения обоих типов имеY ют свойство сваливаться как снег на голову и сопровождаются звуковым сигY налом. Конечно же, сообщение об ошибке не сулит ничего положительного, однако далеко не каждое сообщение является сообщением об ошибке. Один офисный работник постоянно жаловался своему начальнику на сбои, которые возникают при выполнении разработанного нами макроса. На самом же деле это были обычные информационные сообщения. Попросите заказчика связаться с вами по телефону, если на экране возникY нет окно сообщения об ошибке. Узнайте номер ошибки и ее описание, после чего попросите заказчика щелкнуть на кнопке Debug (Отладка), сообщить имя модуля, процедуры и содержимое строки, выделенной желтым цветом. ВооруY жившись полученной информацией, постарайтесь определить причину сбоя. “Отложенные” ошибки Запуская программный код в первый раз, его разработчик ожидает возникY новения внештатных ситуаций. Для обнаружения ошибок на этапе тестироваY ния кода последний часто выполняют в пошаговом режиме. К сожалению, тестирование и отладка не гарантируют отсутствие ошибок на этапе эксплуатации программы. Иногда первый сбой может произойти поY сле нескольких месяцев ее успешного применения. 570 Часть III Удивительные возможности Visual Basic for Applications Не спешите перекладывать ответственность за неработоспособность кода на заказчика. Зачастую пристальное изучение ошибки приводит к выявлению слабых мест, не учтенных на этапе разработки. В следующих разделах рассматриваются примеры внештатных ситуаций, способных возникнуть на этапе эксплуатации программного кода. Ошибка времени выполнения 9: “Subscript out of range” Предположим, что переданная заказчику рабочая книга содержит лист Меню. Через некоторое время заказчик сообщает о сбое и присылает вам коY пию экрана, показанную на рис. 23.8. Рис. 23.8. Одной из распространенных причин возникновения ошибки времени выполнения 9 является попытка обращения к несуществующе& му рабочему листу Приведенный ниже код предполагает наличие рабочего листа Меню. Если заказчик удалил или переименовал рабочий лист Меню, возникнет ошибка. Sub GetSettings() ThisWorkbook.Worksheets("Меню").Select x = Range("A1").Value End Sub К сожалению, описанная ситуация достаточно типична. Следующий код позволяет заменить недружественное сообщение об ошибке редактора VBA сообщением об отсутствии рабочего листа Меню. Sub GetSettingsLonger() On Error Resume Next x = ThisWorkbook.Worksheets("Меню").Name If Not Err.Number = 0 Then MsgBox "Рабочий лист ""Меню"" не обнаружен" Exit Sub End If On Error GoTo 0 Обработка ошибок Глава 23 571 ThisWorkbook.Worksheets("Меню").Select x = Range("A1").Value End Sub Ошибка времени выполнения 1004: “Method 'Range' of object '_Global' failed” Предположим, что программный код ежедневно импортирует в Excel соY держимое некоторого текстового файла, загружаемого с FTPYсервера. Макрос SetReportInItalics выделяет курсивом последнюю строку полученного результата, как показано ниже. Sub SetReportInItalics() ' Выполнение этого макроса вызовет сбой при условии, ' что текстовый файл не был импортирован в Excel. TotalRow = Worksheets("Лист1").Range("F65536").End(xlUp).Row FinalRow = TotalRow - 1 Range("A1:A" & FinalRow).Font.Italic = True End Sub Через некоторое время, прошедшее с начала эксплуатации программного кода, заказчик сообщает об ошибке времени выполнения 1004 (рис. 23.9). Рис. 23.9. Ошибка времени выполнения 1004 может быть вызвана 1001 причиной Подобная ошибка может возникнуть при условии, что импортированный текстовый файл не содержал данных. В этом случае значение переменной TotalRow будет равно 1, а переменной FinalRow — 0. Попытка обращения к несуществующей строке приведет к сбою. Следующий код позволяет заменить сообщение об ошибке редактора VBA сообщением о необходимости проверки правильности загрузки текстового файла с FTPYсервера. Sub SetReportInItalicsNoError() TotalRow = Worksheets("Лист1").Range("A65536").End(xlUp).Row FinalRow = TotalRow - 1 If FinalRow > 0 Then Range("A1:A" & FinalRow).Font.Italic = True Else MsgBox "Импортированный файл не содержит данных. _ Попробуйте загрузить файл с FTP-сервера еще раз" End If End Sub 572 Часть III Удивительные возможности Visual Basic for Applications Несовершенство защиты проекта VBA Проект VBA можно защитить от просмотра. В этом случае окно сообщения об ошибке редактора VBA будет содержать нефункциональную кнопку Debug (Отладка), лишающую пользователя возможности сообщить разработчику доY полнительную информацию о произошедшем сбое. Вдобавок, защита, обеспечиваемая редактором VBA, далеко не совершенY на. Программисты из Эстонии создали приложение, которое взламывает заY щиту любого проекта Excel. Другими словами, не усложняйте себе жизнь, прибегая к защите проектов VBA. Практикум Взлом паролей Взлом паролей в Excel 97 и Excel 2000 не представлял никаких трудностей. Специ& альные программы в мгновение ока сообщали пользователю пароль проекта VBA. В Excel 2002 корпорация Microsoft предложила схему защиты, стойкую к атакам программ для взлома паролей. Благодаря применению шифрования единствен& ным способом взлома пароля проекта VBA на протяжении нескольких месяцев по& сле выхода Excel 2002 оставался способ простого подбора. И если для взлома 4&значного пароля наподобие blue требовалось 10 минут, то взлом 24&значного пароля наподобие *A6%kJJ542(9$GgU44#2drt8 занимал около 20 часов. Безус& ловно, это затрудняло доступ к коду программистам Excel, желавшим воспользо& ваться чужой интеллектуальной собственностью. К сожалению, следующее поколение программ для взлома паролей справилось с 24&значным паролем Excel 2002 за 2 секунды. Поначалу сообщение программы для взлома пароля о том, что она определила искомый 24&значный пароль как XVII, кажется ошибкой. Проверив полученный результат, убеждаешься в обрат& ном. Дело в том, что вместо применения метода простого подбора программа ге& нерирует новый 4&значный пароль проекта VBA и сохраняет его в файле рабочей книги Excel. Подобный подход к взлому пароля может породить весьма пикантную ситуацию. Предположим, что разработчик устанавливает пароль проекта VBA *A6%kJJ542(9$GgU44#2drt8 и передает файл заказчику. Если заказчик изменит пароль, а затем обратится за помощью к разработчику, он будет вынужден пере& слать ему файл рабочей книги Excel, защищенный новым паролем. Единственный, кто способен извлечь выгоду из такой ситуации, — это талантливый программист из Эстонии. На момент написания этой книги число VBA&проектов Excel превышало число раз& работчиков, способных их реализовать. Большинство разработчиков отдает себе отчет в том, что созданный ими код может быть доступен любому человеку, у ко& торого имеется файл рабочей книги. Тем не менее, многие начинающие программисты все еще продолжают защищать паролем созданные ими проекты VBA. Рассмотрим следующую ситуацию. Обработка ошибок Глава 23 573 Предположим, что заказчик желает внести изменения в существующий VBA& проект. Он связывается с разработчиком, который выполняет всю необходимую работу. Спустя месяц заказчик вновь обращается к разработчику, который на этот раз не может выполнить задание своевременно. Заказчик не хочет ждать и обра& щается к другому программисту. Поскольку проект VBA защищен паролем, новому разработчику придется его взломать, чтобы получить доступ к программному коду. Если в будущем заказчик планирует сотрудничать с первоначальным создателем кода, может возникнуть ситуация, в которой два разработчика будут иметь два разных пароля для доступа к проекту VBA. В подобной ситуации рекомендуется полностью отказаться от за& щиты программного кода паролем. Защита проекта VBA в различных версиях Excel Схема защиты проекта VBA паролем в Excel 2002 несовместима со схемой защиты проекта VBA паролем в Excel 97. Другими словами, защиту проекта VBA, установленную с помощью Excel 2002, нельзя снять средствами Excel 97. В частности, это приводит к тому, что пользователь Excel 97 не сможет ни отY ладить сбойный код, ни передать разработчику подробную информацию о возникнувшей ошибке. Защита проекта VBA паролем приносит больше неприятностей, чем пользы. Совместимость различных версий Excel Корпорация Microsoft совершенствует VBA в каждой новой версии Excel. В частности, переход от Excel 97 к Excel 2000 был отмечен изменением кода создания сводных таблиц, а также некоторых аспектов работы с диаграммами. Одним из нововведений Excel 2002 является параметр TrailingMinusNumbers метода OpenText. Попытка выполнения кода, расположенного в одном модуле с параметром TrailingMinusNumbers, приведет к ошибке компиляции в Excel 2000. Рассмотрим пример. Предположим, что модуль Module1 содержит макросы ProcA, ProcB и ProcC, а модуль Module2 — макросы ProcD и ProcE. Макрос ProcE включает вызов метода OpenText с параметром TrailingMinusNumbers. Благодаря отсутствию кода, специфичного для Excel 2002, макросы ProcA, ProcB и ProcC могут быть выполнены в Excel 2000 без необходимости внесеY ния какихYлибо изменений, а вот попытка выполнения макроса ProcD приY ведет к ошибке компиляции модуля Module2. Окончательно запутать ситуаY цию способен тот факт, что причина сбоя находится не в макросе ProcD, а в макросе ProcE. Одно из возможных решений описанной выше проблемы заключается в тестировании кода VBA во всех версиях Excel, включая Excel 97. Следует отметить, что Excel 97 с набором исправлений SRY2 намного устойчивее 574 Часть III Удивительные возможности Visual Basic for Applications первоначального выпуска Excel 97. Поскольку далеко еще не все пользоваY тели Excel 97 установили набор исправлений SRY2, тестировать придется оба выпуска. Пользователи Macintosh ошибочно полагают, что версия Excel для MacinY tosh и версия Excel для Windows одинаковы. На самом деле совместимость ExY cel для Macintosh и Excel для Windows заканчивается на формате файлов и пользовательском интерфейсе. Код VBA Windows и Macintosh не совместим. Несмотря на внешнюю схожесть, код VBA Windows и Macintosh отличается множеством мелких аспектов. Следующий шаг Сколько бы усилий ни было затрачено на тестирование и отладку проY граммного кода, рано или поздно он даст сбой. В этой главе были рассмотреY ны различные способы обработки ошибок, отладка кода и другие вопросы, связанные с возникновением непредвиденных ситуаций. Следующая глава посвящена созданию пользовательских меню и панелей инструментов. Глава 24 Ñîçäàíèå ïîëüçîâàòåëüñêèõ ìåíþ è ïàíåëåé èíñòðóìåíòîâ В этой главе рассматривается созY дание пользовательских меню и паY нелей инструментов, предназначенY ных для запуска макросов. В конце главы будут описаны способы запусY ка макросов с помощью сочетания клавиш, кнопки и элемента управлеY ния ActiveX. Создание пользовательского меню Пользовательское меню прекрасY но подходит для размещения команд запуска макросов, созданных для заY казчика. На рис. 24.1 показано польY зовательское меню, разработанное для вымышленной компании XYZ. Создание и удаление пользовательского меню Строка главного меню Excel предY ставлена объектом VBA Application.CommandBars(1) и включает такие меню, как Файл (File), Правка (Edit), Вид (View) и т.д. Чтобы добаY вить к главному меню новый пункт, воспользуйтесь методом Add. При добавлении нового пункта меню неY обходимо указать его имя и располоY жение. 24 Создание пользовательского меню ...............................................575 Создание пользовательской панели инструментов ................. 581 Другие способы запуска макросов ........................................587 Следующий шаг........................... 592 576 Часть III Удивительные возможности Visual Basic for Applications Рис. 24.1. Пользовательское меню, названное по имени заказчика, подчеркивает уникальность разработанного для него приложения Имя пункта меню (например, XYZ Co) отображается в строке меню. БольY шинству пунктов меню сопоставлено некоторое сочетание клавиш, такое как <Alt+В> (<Alt+V>) для меню Вид (View). Сочетание клавиш, назначенное тоY му или иному пункту меню, определяет подчеркнутая буква в его имени. ЧтоY бы задать сочетание клавиш для пункта меню, поставьте символ амперсанда (&) перед требуемой буквой, например, &XYZ Co. Следует помнить, что сочетания клавиш <Alt+А> (<Alt+I>), <Alt+В> (<Alt+V>), <Alt+Д> (<Alt+D>), <Alt+Е> (<Alt+T>), <Alt+М> (<Alt+O>), <Alt+О> (<Alt+W>), <Alt+П> (<Alt+E>), <Alt+С> (<Alt+H>) и <Alt+Ф> (<Alt+F>) зарезервированы для стандартных меню Excel. Несмотря на то, что одно и то же сочетание клавиш может быть назначено двум различным пункY там меню, оно не будет функциональным. По умолчанию строка меню Excel содержит 10 пунктов: значок Excel, Файл (File), Правка (Edit), Вид (View), Вставка (Insert), Формат (Format), Сервис (Tools), Данные (Data), Окно (Window) и Справка (Help). Чтобы добавить ноY вый пункт меню перед пунктом Справка, установите значение параметра Before метода Add равным 10. Чтобы добавить новый пункт меню после пункта Справка, установите значение параметра Before метода Add равным 11. ДоY бавление нового пункта меню после пункта Справка в Excel для Macintosh приведет к возникновению ошибки, поскольку в MacintoshYприложениях пункт Справка всегда должен оставаться последним пунктом меню. Рекомендуется предусмотреть процедуры создания и удаления меню. ПроY цедуру создания меню следует вызвать в обработчике события Workbook_Open, Workbook_Activate или Worksheet_Activate, а процедуру удаления меню YYYY в обработчике события Workbook_BeforeClose. Чтобы не допустить дублирования меню, вызовите в самом начале процеY дуры создания меню процедуру его удаления. Создание пользовательских меню и панелей инструментов Глава 24 577 Ниже приведен код макроса, создающего меню (CreateMenu), и макроса, удаляющего меню (DeleteMenu). Объектная переменная MenuObject имеет тип CommandBarPopup. Макрос CreateMenu вызывает макрос DeleteMenu, после чего добавляет в строку главного меню Excel пункт XYZ Co перед пункY том Справка. Sub DeleteMenu() ' Этот макрос должен быть выполнен ' перед закрытием рабочей книги. On Error Resume Next Application.CommandBars(1).Controls("&XYZ Co").Delete On Error GoTo 0 End Sub Sub CreateMenu() Dim MenuObject As CommandBarPopup Dim MenuItem As Object Dim SubMenuItem As Object ' Удалить существующее меню. Call DeleteMenu Set MenuObject = Application.CommandBars(1). _ Controls.Add(Type:=msoControlPopup, _ Before:=10, Temporary:=True) MenuObject.Caption = "&XYZ Co" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ImportData" MenuItem.Caption = "&Импорт данных" End Sub Добавление команд меню Чтобы добавить команду меню, воспользуйтесь методом MenuObject.Controls.Add, установив значение параметра Type равным msoControlButton. MenuObject — это созданный ранее объект меню типа CommandBarPopup. Объект команды меню имеет свойство Caption, опредеY ляющее текст команды, и OnAction, определяющее макрос, который будет выполнен при выборе этой команды. Следующий код создает меню XYZ Co, содержащее четыре команды, как показано на рис. 24.2. Sub CreateSimpleMenu() Dim MenuObject As CommandBarPopup Dim MenuItem As Object Dim SubMenuItem As Object ' Удалить существующее меню. Call DeleteMenu Set MenuObject = Application.CommandBars(1). _ 578 Часть III Удивительные возможности Visual Basic for Applications Controls.Add(Type:=msoControlPopup, _ Before:=10, Temporary:=True) MenuObject.Caption = "&XYZ Co" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ImportData" MenuItem.Caption = "&Импорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ExportData" MenuItem.Caption = "&Экспорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ProduceReport" MenuItem.Caption = "От&чет" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "AboutMe" MenuItem.Caption = "О про&грамме" End Sub Группирование команд меню Команды меню можно группировать. Чтобы создать линию раздела двух групп команд меню, установите значение свойства BeginGroup первой коY манды меню, расположенной за линией, равным True. Следующий код поY мещает линию раздела между третьей и четвертой командой меню, как покаY зано на рис. 24.3. Рис. 24.2. Меню, со& держащее четыре ко& манды Рис. 24.3. Чтобы создать линию раздела двух групп команд ме& ню, воспользуйтесь свойством BeginGroup Sub CreateGroupedMenu() ' Этот макрос помещает линию раздела двух групп ' команд меню перед командой "О программе". Dim MenuObject As CommandBarPopup Dim MenuItem As Object Dim SubMenuItem As Object ' Удалить существующее меню. Call DeleteMenu Set MenuObject = Application.CommandBars(1). _ Controls.Add(Type:=msoControlPopup, _ Before:=10, Temporary:=True) Создание пользовательских меню и панелей инструментов Глава 24 579 MenuObject.Caption = "&XYZ Co" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ImportData" MenuItem.Caption = "&Импорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ExportData" MenuItem.Caption = "&Экспорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ProduceReport" MenuItem.Caption = "От&чет" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "AboutMe" MenuItem.Caption = "О про&грамме" ' Следующая строка создает линию раздела. MenuItem.BeginGroup = True End Sub Создание подменю Подменю позволяет сократить число команд меню путем вынесения неY скольких команд в отдельный пункт. На рис. 24.4 показано подменю Отчет, содержащее команды для создания всевозможных отчетов. Рис. 24.4. Как правило, подменю используется для ло& гического группирования команд меню Чтобы создать подменю, воспользуйтесь методом MenuObject.Controls.Add, установив значение параметра Type равным msoControlPopup. MenuObject — это созданный ранее объект меню типа CommandBarPopup. Чтобы добавить команду подменю, воспользуйтесь методом MenuItem.Controls.Add, установив значение параметра Type равным msoControlButton. MenuItem — это созданный ранее объект подменю типа Object. 580 Часть III Удивительные возможности Visual Basic for Applications Приведенный ниже код создает подменю, показанное на рис. 24.4. Sub CreateFullMenu() Dim MenuObject As CommandBarPopup Dim MenuItem As Object Dim SubMenuItem As Object ' Удалить существующее меню. Call DeleteMenu Set MenuObject = Application.CommandBars(1). _ Controls.Add(Type:=msoControlPopup, _ Before:=10, Temporary:=True) MenuObject.Caption = "&XYZ Co" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ImportData" MenuItem.Caption = "&Импорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "ExportData" MenuItem.Caption = "&Экспорт данных" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlPopup) MenuItem.Caption = "От&чет" MenuItem.BeginGroup = True Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "StyleRptNew" SubMenuItem.Caption = "Отчет о товарах" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "CategoryRptNew" SubMenuItem.Caption = "Отчет о группах товаров" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "ReportCard" SubMenuItem.Caption = "Карточка отчета" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "OverRpt" SubMenuItem.Caption = "Отчет о товарах за год" SubMenuItem.BeginGroup = True Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "CreateReportCard" SubMenuItem.Caption = "Отчет о магазинах" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "InventoryAgingReportNew" SubMenuItem.Caption = "Отчет о складских запасах" Создание пользовательских меню и панелей инструментов Глава 24 581 SubMenuItem.BeginGroup = True Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "ShowStockReport" SubMenuItem.Caption = "Магазины с критическим _ уровнем запасов" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "ShowRecTransfer" SubMenuItem.Caption = "Рекомендуемые перемещения товаров" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "ShowSummaryNew" SubMenuItem.Caption = "Итоговый отчет" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlPopup) MenuItem.Caption = "&Диаграмма" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "GraphOne" SubMenuItem.Caption = "Диаграмма &продаж" Set SubMenuItem = MenuItem.Controls.Add(Type:= _ msoControlButton) SubMenuItem.OnAction = "GraphTwo" SubMenuItem.Caption = "Диаграмма &запасов" Set MenuItem = MenuObject.Controls.Add(Type:=msoControlButton) MenuItem.OnAction = "AboutMe" MenuItem.Caption = "О про&грамме" MenuItem.BeginGroup = True End Sub Создание пользовательской панели инструментов Команды запуска макросов, созданных для заказчика, можно также разY мещать на панели инструментов. На рис. 24.5 показан пример пользовательY ской панели, содержащей несколько различных элементов управления. Рис. 24.5. Кнопка, размещенная на панели инстру& ментов, может содержать только текст, только значок или же и значок, и текст. Чтобы задать текст всплы& вающей подсказки, воспользуйтесь свойством эле& мента управления ToolTipText 582 Часть III Удивительные возможности Visual Basic for Applications Создание и удаление пользовательской панели инструментов По аналогии с пользовательским меню, предусмотрите процедуры для созY дания и удаления панели инструментов. Процедуру создания панели инструY ментов следует вызвать в обработчике события Workbook_Open, Workbook_Activate или Worksheet_Activate, а процедуру удаления панели инструментов YYYY в обработчике события Workbook_BeforeClose. Макрос CreateToolbar создает плавающую панель инструментов. ПреY дыдущие версии Excel позволяли закреплять панель инструментов, однако деY лать этого не рекомендуется. Чтобы создать панель инструментов, воспользуйтесь методом CommandBars.Add, указав имя и тип панели инструментов. Ниже приведен код макY росов CreateToolbar и DeleteToolbar. Sub DeleteToolbar() On Error Resume Next CommandBars("XYZ").Delete On Error GoTo 0 End Sub Sub CreateToolbar() Dim Tbar As CommandBar Dim NewDD As CommandBarControl Dim NewBtn As CommandBarButton ' Удалить существующую панель инструментов. DeleteToolbar ' Создать панель инструментов. Set Tbar = CommandBars.Add With Tbar .Name = "XYZ" .Visible = True .Position = msoBarFloating End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .OnAction = "PrintCard" .Caption = "Печать..." .TooltipText = "Выберите страницы для печати" .Style = msoButtonCaption End With End Sub Добавление кнопок на панель инструментов Чтобы добавить кнопку на созданную ранее панель инструментов TBar, воспользуйтесь методом TBar.Controls.Add. Макрос, который будет выY полнен в результате щелчка на кнопке, определяет ее свойство OnAction. Создание пользовательских меню и панелей инструментов Глава 24 583 Кроме того, кнопке можно назначить текст (свойство Caption), значок (свойство FaceID) и всплывающую подсказку (свойство ToolTipText). Свойство Style определяет содержимое кнопки YYYY только текст (msoButtonCaption), только значок (msoButtonIcon) или же и текст, и значок (msoButtonIconAndCaption). Как показано на рис. 24.5, кнопка Печать содержит только текст, кнопка Фотографии YYYY текст и значок, а кнопY ка Отчеты YYYY только значок. Следующий код создает панель инструментов, содержащую кнопки Печать, Фотографии и Отчеты. Sub CreateToolbar() Dim Tbar As CommandBar Dim NewDD As CommandBarControl Dim NewBtn As CommandBarButton ' Удалить существующую панель инструментов. DeleteToolbar ' Создать панель инструментов. Set Tbar = CommandBars.Add With Tbar .Name = "XYZ" .Visible = True .Position = msoBarFloating End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .OnAction = "PrintCard" .Caption = "Печать..." .TooltipText = "Выберите страницы для печати" .Style = msoButtonCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 682 .OnAction = "DispPictures" .Caption = "Фотографии" .TooltipText = "Показать фотографии выбранных товаров" .Style = msoButtonIconAndCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 461 .OnAction = "ProduceReport" .Caption = "Отчеты" .TooltipText = "Создать отчеты" .Style = msoButtonIcon End With End Sub 584 Часть III Удивительные возможности Visual Basic for Applications Выбор значка кнопки панели инструментов Значок кнопки панели инструментов определяется значением свойства кнопки FaceID. При выборе значения свойства FaceID рекомендуется восY пользоваться обозревателем значков. Один из бесплатных обозревателей значков доступен по адресу: http://skp.mvps.org/faceid.htm. Добавление раскрывающегося списка на панель инструментов Добавим на панель инструментов раскрывающийся список, содержащий имена всех видимых листов рабочей книги Excel. При выборе имени рабочего листа из списка выполняется макрос GoToSheet, делающий выбранный раY бочий лист активным. Следующий код создает панель инструментов, содерY жащую кнопки Печать, Фотографии, Отчеты, а также раскрывающийся спиY сок Перейти к рабочему листу. Sub CreateToolbar() Dim Tbar As CommandBar Dim NewDD As CommandBarControl Dim NewBtn As CommandBarButton Dim Sh As Worksheet ' Удалить существующую панель инструментов. DeleteToolbar ' Создать панель инструментов. Set Tbar = CommandBars.Add With Tbar .Name = "XYZ" .Visible = True .Position = msoBarFloating End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .OnAction = "PrintCard" .Caption = "Печать..." .TooltipText = "Выберите страницы для печати" .Style = msoButtonCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 682 .OnAction = "DispPictures" .Caption = "Фотографии" .TooltipText = "Показать фотографии выбранных товаров" .Style = msoButtonIconAndCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 461 Создание пользовательских меню и панелей инструментов Глава 24 585 .OnAction = "ProduceReport" .Caption = "Отчеты" .TooltipText = "Создать отчеты" .Style = msoButtonIcon End With Set NewDD = Tbar.Controls.Add(Type:=msoControlDropdown) With NewDD .Caption = "Перейти к рабочему листу" .OnAction = "GoToSheet" .Style = msoButtonAutomatic For Each Sh In ActiveWorkbook.Worksheets If Sh.Visible = True Then .AddItem Sh.Name End If Next Sh .ListIndex = 1 .Width = 110 End With End Sub Ниже приведен код макроса GoToSheet. Sub GoToSheet() Dim ThisItem As Integer Dim ThisName As String With CommandBars("XYZ").Controls("Перейти к рабочему листу") ThisItem = .ListIndex ThisName = .List(.ListIndex) End With ' Сделать лист активным. Worksheets(ThisName).Activate End Sub Сохранение и восстановление координат панели инструментов Координаты панели инструментов изменяются каждый раз при ее создаY нии. Добавим к макросам DeleteToolbar и CreateToolbar код, позвоY ляющий сохранить и восстановить координаты панели инструментов. Public Sub DeleteToolbar() Dim obj As CommandBar On Error Resume Next Set obj = CommandBars("XYZ") If Err.Number = 0 Then ' Запомнить текущие координаты ' панели инструментов. With ThisWorkbook.Sheets("Параметры") .[I5].Value = obj.Top .[I6].Value = obj.Left End With End If obj.Delete End Sub 586 Часть III Удивительные возможности Visual Basic for Applications Sub CreateToolbar() Dim nTop As Variant Dim nLeft As Variant Dim Tbar As CommandBar Dim NewDD As CommandBarControl Dim NewBtn As CommandBarButton Dim Sh As Worksheet ' Удалить существующую панель инструментов. DeleteToolbar With ThisWorkbook.Sheets("Параметры") nTop = .[I5].Value nLeft = .[I6].Value End With ' Создать панель инструментов. Set Tbar = CommandBars.Add With Tbar .Name = "XYZ" .Visible = True .Position = msoBarFloating If Not IsEmpty(nTop) Then .Top = nTop If Not IsEmpty(nLeft) Then .Left = nLeft End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .OnAction = "PrintCard" .Caption = "Печать..." .TooltipText = "Выберите страницы для печати" .Style = msoButtonCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 682 .OnAction = "DispPictures" .Caption = "Фотографии" .TooltipText = "Показать фотографии выбранных товаров" .Style = msoButtonIconAndCaption End With Set NewBtn = Tbar.Controls.Add(Type:=msoControlButton) With NewBtn .FaceId = 461 .OnAction = "ProduceReport" .Caption = "Отчеты" .TooltipText = "Создать отчеты" .Style = msoButtonIcon End With Set NewDD = Tbar.Controls.Add(Type:=msoControlDropdown) With NewDD .Caption = "Перейти к рабочему листу" .OnAction = "GoToSheet" .Style = msoButtonAutomatic Создание пользовательских меню и панелей инструментов Глава 24 587 For Each Sh In ActiveWorkbook.Worksheets If Sh.Visible = True Then .AddItem Sh.Name End If Next Sh .ListIndex = 1 .Width = 110 End With End Sub Другие способы запуска макросов Помимо меню и панели инструментов, для запуска макроса можно испольY зовать сочетание клавиш, кнопку и элемент управления ActiveX. Запуск макроса с помощью сочетания клавиш Чтобы назначить сочетание клавиш для запуска макроса, выберите имя макроса в диалоговом окне Макрос (Macro) и щелкните на кнопке Параметры (Options). (Чтобы открыть диалоговое окно Макрос, выберите команду главY ного меню Excel Сервис Макрос Макросы (Tools Macro Macros) или наY жмите комбинацию клавиш <Alt+F8>.) Задайте сочетание клавиш, как покаY зано на рис. 24.6. Рис. 24.6. Чтобы выполнить макрос Jump, нажмите сочетание клавиш <Ctrl+J> Внимание Будьте внимательны при выборе сочетания клавиш для запуска макроса. Многие со& четания клавиш (например, <Ctrl+C>) зарезервированы для выполнения стандартных операций в Windows. Относительно безопасными сочетаниями клавиш в Excel 2003 являются сочетания <Ctrl+E>, <Ctrl+J>, <Ctrl+M>, <Ctrl+Q> и <Ctrl+T>. Запуск макроса с помощью кнопки На рабочем листе Excel можно разместить два типа кнопок: стандартную кнопку пользовательской формы и кнопку элемента управления ActiveX. 588 Часть III Удивительные возможности Visual Basic for Applications Выполните следующие действия, чтобы вынести команду добавления кнопки пользовательской формы на панель управления Excel и разместить на рабочем листе кнопку запуска макроса. 1. Щелкните на панели инструментов Excel правой кнопкой мыши и выY берите команду контекстного меню Настройка (Customize). 2. Выберите категорию Формы (Forms) из списка Категории (Categories) вкладки Команды (Commands) диалогового окна Настройка (Customize). 3. Перетащите команду Кнопка (Button) из списка Команды на панель инY струментов Excel. 4. Щелкните на кнопке Закрыть (Close), чтобы закрыть диалоговое окно Настройка. 5. Щелкните на кнопке Кнопка, вынесенной на панель инструментов. 6. С помощью указателя мыши нарисуйте на рабочем листе контур кнопки. 7. Выберите имя макроса в открывшемся диалоговом окне Назначьте макрос объекту (Assign Macro) и щелкните на кнопке OK. 8. Выделите текст кнопки и измените его на более значащий. 9. Чтобы изменить цвет, шрифт и другие параметры внешнего вида кнопY ки, щелкните на ней правой кнопкой мыши и выберите команду конY текстного меню Формат объекта (Format Control). 10. Чтобы назначить кнопке другой макрос, щелкните на ней правой кнопY кой мыши и выберите команду контекстного меню Назначить макрос (Assign Macro). Внимание Существует две версии диалогового окна Формат элемента управления (Format Control). Одна из них содержит семь вкладок (Шрифт (Font), Выравнивание (Alignment), Размер (Size), Защита (Protection), Свойства (Properties), Поля (Margins) и Веб (Web)), другая — только одну (Шрифт (Font)). В результате щелчка правой кнопкой мыши над элементом управления вокруг него появляется рамка, заполненная диагональными отрезками (рис. 24.7) или точками (рис. 24.8). В первом случае выбор команды контекстного меню Формат объекта приводит к отображению диалогового окна Формат элемента управления с одной вкладкой, во втором — с семью. Чтобы изменить заливку рамки элемента управления с диагональных отрезков на точки, щелкните на рамке левой кнопкой мыши. Помимо кнопки, макрос можно назначить любому графическому объекту, расположенному на рабочем листе Excel. Например, чтобы назначить макрос автофигуре, щелкните на ней правой кнопкой мыши и выберите команду конY текстного меню Назначить макрос (Assign Macro), как показано на рис. 24.9. Создание пользовательских меню и панелей инструментов Рис. 24.7. Выбор команды контекстного меню Формат объекта приведет к отображению диа& логового окна Формат элемента управления, содержащего одну вкладку Рис. 24.8. Выбор команды контекстного меню Формат объекта приведет к отображению диа& логового окна Формат элемента управления, содержащего семь вкладок Рис. 24.9. Макрос можно назначить любому графическому объекту, расположенному на ра& бочем листе Excel Глава 24 589 590 Часть III Удивительные возможности Visual Basic for Applications Назначение макроса автофигуре можно реализовать с помощью программY ного кода, создав автофигуру и присвоив значение ее свойству OnAction. Тем не менее, этот метод имеет один существенный недостаток: если назначаемый макрос находится в другой рабочей книге Excel, то при ее сохранении и заY крытии Excel изменит значение свойства OnAction, включив в него полный путь к содержащей макрос рабочей книге. Запуск макроса с помощью элемента управления ActiveX Чтобы запустить макрос с помощью элемента управления ActiveX, создайте процедуру ИмяЭлементаУправленияActiveX_Click, содержащую вызов макроса или его код. Для этого выполните следующие действия. 1. Выберите команду главного меню Excel Вид Панели инструментов Элементы управления (View Toolbars Control Toolbox). 2. Щелкните на кнопке Кнопка (Command Button), расположенной на паY нели инструментов Элементы управления. С помощью указателя мыY ши нарисуйте на рабочем листе контур кнопки. 3. Чтобы изменить параметры элемента управления ActiveX, щелкните на кнопке Свойства (Properties), расположенной на панели инструментов Элементы управления. На экране появится окно свойств, позволяюY щее задать такие параметры элемента управления, как текст, цвет и др. (рис. 24.10). Рис. 24.10. Окно свойств элемента управления ActiveX позволяет на& строить его параметры Создание пользовательских меню и панелей инструментов Глава 24 591 На заметку Закрытие окна свойств элемента управления ActiveX приводит к закрытию окна свойств редактора VBA. 4. Чтобы назначить макрос элементу управления ActiveX, щелкните на кнопке Исходный текст (View Code), расположенной на панели инстY рументов Элементы управления. Разместите в тексте обработчика соY бытия ИмяЭлементаУправленияActiveX_Click вызов требуемого макроса или его код, как показано на рис. 24.11. Рис. 24.11. Чтобы ввести код обработчика собы& тия ИмяЭлементаУправленияActiveX_Click, щелкните на кнопке Исходный текст, располо& женной на панели инструментов Элементы управления Практикум Запуск макроса с помощью гиперссылки Чтобы запустить макрос с помощью гиперссылки, следует прибегнуть к неболь& шой хитрости. Выделите ячейку, в которой требуется разместить гиперссылку, по& сле чего выберите команду главного меню Excel Вставка Гиперссылка (Insert Hyperlink) или нажмите комбинацию клавиш <Ctrl+K>. В открывшемся диалого& вом окне Добавление гиперссылки (Insert Hyperlink) щелкните на кнопке Местом в документе (Place in This Document). В поле Введите адрес ячейки (Type the cell reference) введите адрес ячейки, в которой находится гиперссылка. Таким обра& зом, гиперссылка будет указывать сама на себя (рис. 24.12). Для запуска требуемого макроса воспользуйтесь обработчиком события Worksheet_FollowHyperlink, как показано ниже: Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink) Select Case Target.TextToDisplay Case "Widgets" RunWidgetReport Case "Gadgets" RunGadgetReport Case "Gizmos" RunGizmoReport Case "Doodads" RunDooDadReport End Select End Sub 592 Часть III Удивительные возможности Visual Basic for Applications Рис. 24.12. Чтобы назначить макрос гиперссылке, создайте гиперссылку, ука& зывающую саму на себя, и воспользуйтесь обработчиком события Worksheet_FollowHyperlink для запуска требуемого макроса Следующий шаг Следующая глава посвящена использованию надстроек в качестве контейY неров для размещения макросов. Глава 25 Íàäñòðîéêè Надстройки YYYY это программы, добавляющие в Excel дополнительY ные команды и возможности. В этой главе рассматриваются стандартные надстройки Excel, котоY рые можно создать средствами VBA. Существует также два других типа надстроек: надстройки COM и надY стройки DLL, однако эти надстройки можно создать только с помощью ViY sual Basic (например, Visual Basic 6 или Visual Basic .NET) или Visual C++. Стандартные надстройки Excel Стандартные надстройки Excel (.xla) могут использоваться в качеY стве контейнера макросов. Ниже пеY речислены основные преимущества надстроек. Выполнение обработчика соY бытия Workbook_Open можY но отменить путем удерживаY ния нажатой клавиши <Shift> при открытии рабочей книги Excel. Этот трюк невозможно проделать с обработчиком соY бытия Workbook_Open, разY мещенным в надстройке. После установки надстройки с помощью диалогового окна Надстройки (AddYIns) она будет загружаться при каждом запуске Excel. (Чтобы открыть диалогоY вое окно Надстройки, выберите команду главного меню Excel Сервис Надстройки (Tools AddYIns).) 25 Стандартные надстройки Excel ................................................ 593 Преобразование рабочей книги Excel в надстройку ........... 594 Использование надстроек .........597 Альтернативное решение: использование скрытой рабочей книги.............................. 599 Следующий шаг............................ 601 594 Часть III Удивительные возможности Visual Basic for Applications Размещенный в надстройке код выполняется вне зависимости от выY бранного уровня безопасности макросов. Обычно область выполнения макросов ограничивается рабочей книY гой, в которой они были объявлены. Код, размещенный в надстройке, доступен всем открытым рабочим книгам Excel. Надстройки не отображаются в списке открытых файлов меню Окно (Window) и не могут быть выведены на экран с помощью команды Окно Отобразить (Window Unhide). Поскольку надстройка является скрытой рабочей книгой, которую нельзя вывести на экран, не существует возможности выделить или активизировать ячейки надстройки посредством программного кода. Вдобавок, сохранение файла надстройки необходимо реализовать в коде самой надстройки, наприY мер, вызвав метод ThisWorkbook.Save в обработчике события Workbook_BeforeClose. Внимание Впервые возможность сохранения файла надстройки была реализована в Excel 97. Преобразование рабочей книги Excel в надстройку Диалоговое окно Надстройки (AddYIns) содержит список доступных надY строек. Имя и описание надстройки задается с помощью диалогового окна свойств файла рабочей книги Excel. Чтобы задать имя и описание надстройки, выберите команду главного меY ню Excel Файл Свойства (File Properties) и перейдите во вкладку Документ (Summary), как показано на рис. 25.1. Введите имя надстройки в поле Название (Title), а ее описание YYYY в поле Заметки (Comments). Существует два способа преобразования рабочей книги Excel в надстройку. Первый способ проще, однако он не лишен некоторых недостатков. Второй способ требует выполнения большего числа действий, но позволяет лучше контролировать процесс создания надстройки. Преобразование рабочей книги Excel в надстройку с помощью диалогового окна “Сохранение документа” (Save As) Чтобы преобразовать рабочую книгу Excel в надстройку с помощью диаY логового окна Сохранение документа, выберите команду главного меню ExY cel Файл Сохранить как (File Save As). Выберите формат Надстройка Microsoft Office Excel (Microsoft Office Excel AddYIn (*.xla)) из раскрывающегоY Надстройки Глава 25 595 ся списка Тип файла (Save as type). Обратите внимание, что папка, в которой будет сохранена надстройка, изменится на AddIns, как показано на рис. 25.2. Рис. 25.1. Заполните поля Название и Заметки перед преобразованием рабочей книги Excel в надстройку Рис. 25.2. По умолчанию надстройки Excel сохраняются в папке AddIns Полный путь к папке AddIns зависит от операционной системы, установY ленной на компьютере. Например, в Windows XP это может быть папка 596 Часть III Удивительные возможности Visual Basic for Applications C:\Documents and Settings\ИмяПользователя\Application Data\ Microsoft\AddIns. После преобразования рабочей книги Excel (.xls) в надстройку (.xla) файл XLS все еще остается открытым. Хранить этот файл не имеет смысла, так как надстройку легко преобразовать в рабочую книгу. Внимание При преобразовании рабочей книги Excel в надстройку с помощью диалогового окна Сохранение документа текущим активным листом должен быть рабочий лист. Если текущим активным листом является лист диаграммы, формат Надстройка Microsoft Office Excel окажется недоступен. Преобразование рабочей книги Excel в надстройку с помощью редактора VBA Откройте рабочую книгу Excel, которую необходимо преобразовать в надY стройку. В окне редактора VBA щелкните на модуле ЭтаКнига (ThisWorkbook) и присвойте параметру IsAddin значение True, как показано на рис. 25.3. Рис. 25.3. Чтобы преобразовать рабочую книгу в надстройку, присвойте параметру IsAddin модуля ЭтаКнига значение True Нажмите комбинацию клавиш <Ctrl+G>, чтобы открыть окно Immediate (Быстрое выполнение). В окне Immediate введите следующую строку и наY жмите клавишу <Enter>, чтобы сохранить файл с расширением .xla. ThisWorkbook.SaveAs FileName:="C:\ClientFiles\Chap25.xla", _ FileFormat:=xlAddIn Надстройки Глава 25 597 Использование надстроек Чтобы установить надстройку, выполните следующие действия. 1. Запустите Excel и выберите команду главного меню Сервис Надстройки (Tools AddYIns). 2. Щелкните на кнопке Обзор (Browse), расположенной в открывшемся диалоговом окне Надстройки (AddYIns) (рис. 25.4). Рис. 25.4. Щелкните на кнопке Обзор, чтобы выбрать файл надстройки 3. Выберите файл надстройки с помощью диалогового окна Обзор (Browse) и щелкните на кнопке OK (рис. 25.5). Рис. 25.5. Выберите файл надстройки и щелкните на кнопке OK 598 Часть III Удивительные возможности Visual Basic for Applications Excel скопирует указанную надстройку в папку AddIns. Имя и описание надстройки будут совпадать со значениями, введенными в поле Название (Title) и Заметки (Comments) вкладки Документ (Summary) диалогового окна свойств файла рабочей книги Excel (рис. 25.6). Рис. 25.6. Надстройка готова к использованию Безопасность стандартных надстроек Excel Чтобы отобразить стандартную надстройку Excel, достаточно открыть окно редактора VBA и присвоить параметру IsAddin модуля ЭтаКнига (ThisWorkbook) значение True. Для защиты проекта VBA выполните следуюY щие действия. 1. Откройте окно редактора VBA. 2. Выберите команду Tools VBAProject Properties (Сервис Свойства проекта VBA). 3. Перейдите во вкладку Protection (Защита). 4. Установите флажок Lock project for viewing (Запретить просмотр проекта). 5. Введите пароль и подтверждение пароля. На заметку Современные программы для взлома паролей справляются с защитой проекта VBA в считанные секунды. Более подробно этот вопрос рассматривается в разделе “Взлом паролей” главы 23 на с. 572. Надстройки Глава 25 599 Выгрузка надстроек Чтобы выгрузить надстройку из памяти, выполните одно из следующих действий. 1. Снимите флажок надстройки в диалоговом окне Надстройки (AddYIns). В результате выполнения этого действия надстройка становится недосY тупной в рамках текущего и всех последующих сеансов работы с Excel. 2. Откройте окно Immediate (Быстрое выполнение) редактора VBA. В окне Immediate введите следующую строку и нажмите клавишу <Enter>: Workbooks("ИмяФайлаНадстройки .xla").Close 3. Закройте Excel. Завершение текущего сеанса работы с Excel приводит к автоматической выгрузке из памяти всех надстроек. Удаление надстроек Чтобы удалить надстройку из списка доступных надстроек диалогового окY на Надстройки (AddYIns), выполните следующие действия. 1. Закройте все открытые сеансы работы с Excel. 2. Отыщите файл надстройки с помощью средства Проводник (Windows Explorer). На компьютере с Windows XP файл надстройки может нахоY диться в папке C:\Documents and Settings\ИмяПользователя\ Application Data\Microsoft\AddIns. 3. Переименуйте файл надстройки или переместите его в другую папку. 4. Запустите Excel. На экране появится сообщение о том, что Excel не моY жет найти файл надстройки. Щелкните на кнопке OK, чтобы закрыть окно сообщения. 5. Выберите команду главного меню Excel Сервис Надстройки (Tools AddYIns). В окне Надстройки снимите флажок надстройки, которую неY обходимо удалить. Excel предупредит об отсутствии надстройки и предY ложит ее удалить. Щелкните на кнопке Да (Yes), чтобы удалить надY стройку из списка доступных надстроек. Альтернативное решение: использование скрытой рабочей книги Одна из ключевых характеристик надстроек состоит в том, что надстройки являются скрытыми рабочими книгами. Однако для создания скрытой рабоY чей книги ее совсем не обязательно преобразовывать в надстройку. Чтобы скрыть рабочую книгу, выберите команду главного меню Excel Окно Скрыть (Window Hide). Сохранить скрытую рабочую книгу можно только с помощью редактора VBA, поскольку стандартные средства Excel сдеY 600 Часть III Удивительные возможности Visual Basic for Applications лать этого не позволяют. Выделите рабочую книгу в окне диспетчера проекY тов, введите следующую строку в окне Immediate (Быстрое выполнение) и нажмите клавишу <Enter>: ThisWorkbook.Save Практикум Размещение программного кода и пользовательских форм в скрытой рабочей книге Разработчики приложений Access размещают данные и программный код в раз& ных базах данных, объединяя их с помощью средства связывания таблиц. Аналогичный подход рекомендуется применять при создании крупных проектов в Excel. Все данные можно разместить в рабочей книге Data.xls, связав ее с рабо& чей книгой Code.xls с помощью небольшого фрагмента программного кода. Преимущество разделения данных и кода заключается в возможности обновления последнего без необходимости изменения пользовательских данных. Рассмотрим следующую ситуацию. Предположим, что некоторое приложение Ex& cel, реализованное в виде одного файла рабочей книги, было передано 50 агентам по продаже товаров, каждый из которых, в свою очередь, распростра& нил его среди 10 крупных заказчиков. Если в приложении будет обнаружена ошибка, ее потребуется исправить во всех 500 копиях рабочей книги. Очевидно, что исходное приложение нуждается в существенной переработке. Создадим две рабочие книги — для хранения данных (Data.xls) и программного кода (Code.xls). Рабочая книга Data.xls будет содержать около 20 строк кода, открывающего рабочую книгу Code.xls и передающего ей управление при от& крытии рабочей книги Data.xls, а также закрывающего рабочую книгу Code.xls при закрытии рабочей книги Data.xls. Разделение данных и кода имеет несколько преимуществ. Во&первых, существен& но уменьшится размер файла данных. Во&вторых, для внесения изменений в при& ложение достаточно исправить код рабочей книги Code.xls и распространить ее среди всех пользователей. Будьте особенно внимательны при разработке и тестировании кода, помещае& мого в рабочую книгу с данными. Ошибка, допущенная в этом коде, может по& влечь за собой необходимость исправления сотен файлов рабочих книг. Ниже приведен пример кода, помещаемого в рабочую книгу Data.xls. Private Sub Workbook_Open() ' Проверить, открыт ли файл Code.xls. On Error Resume Next x = Workbooks("Code.xls").Name ErrHolder = Err.Number On Error GoTo 0 If ErrHolder <> 0 Then ' Открыть рабочую книгу, содержащую программный код. CodeFile = ThisWorkbook.Path & _ Надстройки Глава 25 601 Application.PathSeparator & "Code.xls" On Error Resume Next Workbooks.Open CodeFile ErrHolder = Err.Number On Error GoTo 0 End If ' Выполнить макрос, содержащийся в рабочей книге Code.xls. If ErrHolder = 0 Then Application.Run "'Code.xls'!CustFileOpen" End If End Sub Процедура CustFileOpen может использоваться для создания меню, внесения изменений в файл данных и т.п. Разделение данных и программного кода является ключевым моментом при раз& работке сложного приложения Excel. Для внесения изменений в приложение достаточно исправить код рабочей книги Code.xls и распространить ее среди всех пользователей. Следующий шаг Последняя глава книги представляет собой практикум Тушара Мехты (Tushar Mehta) YYYY независимого консультанта, имеющего статус Microsoft MVP. Тушар демонстрирует процесс создания приложения Excel ‘‘с нуля’’. Глава 26 Ïðàêòèêóì: ñîçäàíèå ïðèëîæåíèÿ Excel “ñ íóëÿ” Эта глава представляет собой практикум Тушара Мехты (Tushar Mehta) YYYY независимого консультанY та, имеющего статус Microsoft MVP. Тушар демонстрирует процесс создаY ния приложения Excel ‘‘с нуля’’, заY трагивая множество различных воY просов проектирования. О Тушаре Мехта Тушар Мехта, магистр менеджY мента, доктор философии, проживаY ет в Рочестере, штат НьюYЙорк, США. ГYн Тушар YYYY независимый консультант, специализирующийся на разработке бизнесYрешений, объеY диняющих в себе технологию, оргаY низационную структуру, маркетинг и производство. Финансовые и эконоY мические результаты предлагаемых Тушаром решений заключаются в повышении производительности, боY лее эффективном маркетинге, росте дохода, снижении операционных расходов и улучшении потребительY ской ценности. Начиная с 2000 года, Тушар ежеY годно удостаивается звания MVP (Most Valuable Professional) от корпоY рации Microsoft. Подобного успеха удалось добиться только 65 разработY 26 О Тушаре Мехта........................... 603 Постановка задачи ..................... 604 Решение......................................... 605 Реализация решения с помощью Excel и VBA .............. 606 Резюме............................................ 614 604 Часть III Удивительные возможности Visual Basic for Applications чикам Excel. С некоторыми из созданных Тушаром решений вы можете ознаY комиться на его личном WebYсайте по адресу: http://www.tusharmehta.com. Имея степень доктора делового администрирования, полученную в униY верситете Рочестера, Тушар также является магистром менеджмента бизнесY школы Йельского университета и бакалавром технологии в области электроY техники (специальность YYYY вычислительная техника) Индийского технологиY ческого института в Бомбее, Индия. Постановка задачи Рассмотрим применение Excel в качестве платформы, используемой для документирования конфигурации системы электрического управления, состоящей из 6000 контактных точек. Каждая контактная точка может быть соединена с другой контактной точкой с помощью некоторого проводника. Предложенное решение должно предусматривать возможность одновременY ного использования всех контактных точек. Идентификация контактных точек осуществляется по следующей схеме. Каждой точке ставится в соответствие комбинация из двух чисел m/nnn, где m принимает значения в диапазоне от 1 до 10 (по горизонтали), а nnn — от 101 до 700 (по вертикали). Каждая из 6000 контактных точек представлена одной ячейкой рабочего листа Excel. Если контактная точка соединена с другой контактной точкой, в соответствующую ей ячейку заносится адрес контактной точки на другом конY це соединения. Контактная точка не может быть использована более чем в одном соединеY нии. Помимо создания, соединения можно разрывать и изменять. Очевидно, что поддержание подобной системы документации вручную представляет соY бой чрезвычайно утомительное занятие с большой вероятностью внесения неY верных данных. Процесс разработки соответствующего решения можно разделить на слеY дующие этапы. Отображение терминологии проблемной области (терминологии заY казчика) на терминологию Excel. Разработка визуального представления проблемной области. Разработка функциональных процедур, соответствующих ключевым возможностям приложения. Разработка процедур VBA, реализующих предусмотренные на предыY дущем этапе функциональные процедуры. Возможно ли автоматизировать подобную систему? И если да, то каким образом? Очевидно, что для решения задачи достаточно реализовать автомаY тическое занесение кода контактной точки А в ячейку контактной точки Б Практикум: создание приложения Excel “с нуля” Глава 26 605 при вводе кода последней в ячейку контактной точки А. И конечно же, нужно запретить установку соединения с контактной точкой Б, если она уже испольY зуется в другом соединении. Чтобы разорвать соединение, пользователь должен удалить содержимое любой из ячеек, соответствующих участвующим в соединении контактным точкам. Например, чтобы разорвать соединение между контактной точкой 1/101 и контактной точкой 10/700, пользователю необходимо удалить содерY жимое ячейки, соответствующей контактной точке 1/101, или ячейки, соотY ветствующей контактной точке 10/700. Система восстановит начальное знаY чение и формат ячеек. Процесс изменения соединения состоит из двух этапов. Первый этап заY ключается в удалении существующего соединения, второй этап YYYY в создании нового соединения. Решение Матрица электрических контактов состоит из 100 элементов по горизонтаY ли и 60 элементов по вертикали. Рабочий лист Excel удовлетворяет этим параY метрам, так как содержит 256 столбцов и 65 536 строк. Создадим матрицу контактных точек путем введения их кодов в формате m/nnn в ячейки рабочего листа, начиная с ячейки A1. Код контактной точки, не участвующей в соединении, выделен светлоYсерым шрифтом, как показано на рис. 26.1. При попытке создания соединения система анализирует предыдущее соY стояние ячейки и ее новое содержимое. Если код соответствующей контактY ной точки был выделен светлоYсерым цветом (‘‘нет соединения’’), проверяетY ся возможность создания указанного соединения. При благоприятном резульY тате содержимое ячеек на обоих концах соединения обновляется и выделяется синим цветом. На рис. 26.2 показан результат соединения контактных точек 1/101 и 1/175. Рис. 26.1. Код контактной точки, не участвую& щей в соединении, выделен светло&серым шрифтом Рис. 26.2. Коды контактных точек, участвую& щих в соединении, выделяются синим цветом Помимо создания нового соединения, система должна обеспечить разрыв и изменение существующего соединения. Разрыв соединения соответствует удаY лению содержимого ячейки с помощью клавиши <Delete> или <Backspace>. 606 Часть III Удивительные возможности Visual Basic for Applications Чтобы изменить соединение, достаточно ввести в ячейку новый код контактной точки. Система разорвет существующее соединение и создаст новое. Предлагаемое решение не обеспечивает защиты от непреднамеренного разрушения матрицы контактных точек. Например, вставка в рабочий лист матрицы контактных точек диапазона ячеек с другого рабочего листа приведет к разрушению матрицы. Целостность матрицы может быть нарушена также в результате удаления ячеек рабочего листа с помощью команды меню Excel. Реализация защиты целостности матрицы контактных точек выходит за рамки данного практикума. Реализация решения с помощью Excel и VBA Один из ключевых моментов этого практикума состоит в использовании обработчиков событий. Известно, что большое число обработчиков событий затрудняет тестирование и отладку программного кода. Таким образом, горазY до проще потратить чуть больше времени на создание надежного кода, чем пытаться отладить сбойный код путем тестирования. Разделим процесс реализации решения на два параллельных потока. С одY ной стороны, при создании кода будет использоваться принцип нисходящего программирования. С другой стороны, по мере конкретизации кода к проекту будут добавляться так называемые ключевые компоненты, выполняющие роль ‘‘мостов’’ между моделью приложения и моделью Excel. Этап 1а: нисходящее программирование Любой обработчик события может быть размещен на одном из трех уровY ней: уровне рабочего листа, уровне рабочей книги или уровне приложения. Воспользуемся обработчиками событий уровня рабочей книги, поскольку, воY первых, это позволит обеспечить поддержку нескольких матриц электричеY ских контактов (каждая матрица размещается на отдельном рабочем листе), а, воYвторых, реализовать обработчики событий уровня рабочей книги проще, чем обработчики событий уровня приложения. Наиболее существенный неY достаток подобного подхода заключается в совмещении кода и данных в одY ной и той же рабочей книге Excel. Поскольку обработчики событий будут выY зываться вне зависимости от того, содержит рабочий лист матрицу электричеY ских контактов или нет, необходимо также создать специальный дескриптор рабочего листа матрицы контактов. Разместите код обработчика события Workbook_SheetChange в модуле ЭтаКнига (ThisWorkbook). Private Sub Workbook_SheetChange( _ ByVal Sh As Object, ByVal Target As Range) If Not TypeOf Sh Is Worksheet Then Exit Sub If Not (Sh.Range(cWSWiringTagAddr).Value = _ cWSWiringTagID) Then Exit Sub '<<<<< End Sub Практикум: создание приложения Excel “с нуля” Глава 26 607 Действие, предпринимаемое в результате изменения содержимого ячейки рабочего листа матрицы контактов (создание нового соединения или разY рыв/изменение существующего), на данном этапе не конкретизируется. Процедура, инициализирующая матрицу, заполняет ячейки рабочего листа соответствующими кодами контактных точек в формате m/nnn, изменяя цвет шрифта ячейки на светлоYсерый. Кроме того, процедура инициализации предлагает удалить существующее содержимое рабочего листа и добавляет к нему дескриптор рабочего листа матрицы контактов. Разместите следующий код в стандартном модуле. Sub createWiringData(aWS As Worksheet) End Sub Sub addWiringWSTag(aWS As Worksheet) End Sub Sub initializeSystem() If Not TypeOf ActiveSheet Is Worksheet Then MsgBox "Активным листом должен быть рабочий лист" Exit Sub '<<<<< End If If Not (ActiveSheet.UsedRange.Address = "$A$1" And _ IsEmpty(Range("a1"))) Then If MsgBox("Рабочий лист содержит данные. " _ & "Щелкните на кнопке OK, чтобы удалить их", vbOKCancel) _ = vbCancel Then Exit Sub '<<<<< End If createWiringData ActiveSheet addWiringWSTag ActiveSheet End Sub Код процедур createWiringData и addWiringWSTag будет создан на следующих этапах разработки приложения. Этап 1б: создание ключевых компонентов Создадим ключевой компонент, преобразующий коды контактных точек m/nnn в адреса ячеек рабочего листа Excel, и наоборот. Адрес ячейки рабочего листа Excel состоит из двух частей: номера строки и номера столбца. Функция XLtoWiringCoords преобразует адрес ячейки в код контактной точки, а функция WiringToExcelCoords YYYY код контактной точки в адрес ячейки. Для представления адреса ячейки применяется пользовательский тип ExcelCoords. Разместите следующий код в стандартном модуле. Public Type ExcelCoords Row As Long Col As Long End Type Function XLtoWiringCoords(aRow As Long, aCol As Long) XLtoWiringCoords = CStr((aCol - 1) \ 10 + 1) & "/" _ & CStr(100 + (aRow - 1) * 10 + (aCol - 1) Mod 10 + 1) End Function 608 Часть III Удивительные возможности Visual Basic for Applications Function WiringToExcelCoords(WiringCode As String) As ExcelCoords Dim FirstPart As Long, SecondPart As Long, SlashLoc As Long SlashLoc = InStr(1, WiringCode, "/") If SlashLoc = 0 Then Exit Function On Error Resume Next FirstPart = CLng(Left(WiringCode, SlashLoc - 1)) SecondPart = CLng(Right(WiringCode, Len(WiringCode) - SlashLoc)) On Error GoTo 0 If FirstPart < 1 Or FirstPart > 10 _ Then Exit Function '<<<<< If SecondPart < 101 Or SecondPart > 700 _ Then Exit Function '<<<<< WiringToExcelCoords.Row = (SecondPart - 101) \ 10 + 1 WiringToExcelCoords.Col = (FirstPart - 1) * 10 + _ (SecondPart - 101) Mod 10 + 1 End Function Объявим несколько глобальных констант. В стандартной цветовой палитре Excel светлоYсерый цвет, использующийся для представления свободных конY тактных точек, имеет индекс 15, а синий цвет, использующийся для представY ления занятых контактных точек, YYYY индекс 5. Дескриптор рабочего листа матрицы контактов будет представлен глобальной константой cWSWiringTagID, размещенной в ячейке cWSWiringTagAddr. Поместите следующий код в начало стандартного модуля, содержащего процедуру initializeSystem, а также функции преобразования XLtoWiringCoords и WiringToExcelCoords. Public Const _ cUnusedColorIdx As Integer = 15, _ cInUseColorIdx As Integer = 5, _ cWSWiringTagID As String = "Матрица электрических контактов", _ cWSWiringTagAddr As String = "A61" Первый этап разработки приложения завершен. Следует отметить, что созY данный код не только компилируется, но даже выполняется! Этап 2а: нисходящее программирование Создадим код процедуры addWiringWSTag, объявленной на предыдущем этапе разработки. Sub addWiringWSTag(aWS As Worksheet) Application.EnableEvents = False On Error GoTo ErrHandler aWS.Range(cWSWiringTagAddr).Value = cWSWiringTagID Application.EnableEvents = True Exit Sub ErrHandler: MsgBox "Непредвиденная ошибка в процедуре addWiringWSTag:" _ & vbNewLine & "Ошибка=" & Err.Description & " (" & Err.Number & ")" Application.EnableEvents = True End Sub Практикум: создание приложения Excel “с нуля” Глава 26 609 Процедура createWiringData создает матрицу кодов контактных точек (значений в формате m/nnn), начиная со значения 1/101 в ячейке A1 и заканY чивая значением 10/700 в ячейке CV60. Указанная матрица состоит из 10 блоков значений от m/101 до m/700 (m=1, 2,..., 10), каждый из которых представлен в Excel диапазоном ячеек, содержащим 60 строк и 10 столбцов. Следующий код создает блок значений nnn, копирует его 9 раз (таким обраY зом, всего будет создано 10 таких блоков) и добавляет соответствующее значеY ние m/ к каждому блоку. Sub createWiringData(aWS As Worksheet) Dim Dest As Range, i As Long 'Процедура createWiringData создает матрицу кодов _ контактных точек (значений в формате m/nnn), начиная _ со значения 1/101 в ячейке A1 и заканчивая значением 10/700 _ в ячейке CV60. Указанная матрица состоит из 10 блоков значений _ от m/101 до m/700 (m=1, 2,..., 10), каждый из которых представлен _ в Excel диапазоном ячеек, содержащим 60 строк и 10 столбцов. _ Следующий код создает блок значений nnn, копирует его 9 раз (таким _ образом, всего будет создано 10 таких блоков) и добавляет _ соответствующее значение m/ к каждому блоку. При создании _ nnn-блоков используется общий формат чисел, а при добавлении _ значения m/ - текстовый формат ячеек, иначе выражение m/nnn будет _ интерпретировано как деление двух чисел. Application.EnableEvents = False On Error GoTo ErrHandler resetFormatting aWS createOneDataBlock aWS cloneDataBlocks aWS addMSlash aWS Application.EnableEvents = True Exit Sub ErrHandler: MsgBox "Непредвиденная ошибка в процедуре createWiringData:" _ & vbNewLine & "Ошибка=" & Err.Description & " (" & Err.Number & ")" Application.EnableEvents = True End Sub Последней процедурой, созданной на первом этапе и нуждающейся в конY кретизации, является обработчик события Workbook_SheetChange, срабаY тывающий при изменении содержимого ячейки рабочего листа Excel. Если новое значение ячейки отличается от старого, необходимо определить тип действия: создание нового соединения, разрыв существующего соединения или изменения существующего соединения. Изменение значения ячейки, соответствующей незанятой контактной точке (светлоYсерый цвет шрифта), свидетельствует о создании нового соединения. Разрыв соединения осуществляется путем удаления содержимого ячейки с помощью нажатия клавиши <Delete> или <Backspace>. Таким образом, о разY рыве соединения свидетельствует пустая ячейка. Все остальные действия классифицируются как попытка изменения сущеY ствующего соединения. Если пользователь попытается нарушить целостность матрицы, его нужно уведомить об этом и восстановить предыдущее значение ячейки. 610 Часть III Удивительные возможности Visual Basic for Applications Чтобы сохранить предыдущее значение ячейки, воспользуемся обработчиY ком события Workbook_SheetSelectionChange, срабатывающим при выY делении ячейки. Этап 2б: создание ключевых компонентов Создадим обработчик события Workbook_SheetSelectionChange, соY храняющий предыдущее значение ячейки при ее выделении. Возможность выY деления пользователем нескольких ячеек следует запретить, так как это привеY дет к существенному усложнению программного кода. Ниже приведен код проY цедур Workbook_SheetChange, Workbook_SheetSelectionChange, ‘‘загоY товки’’ процедур создания и разрыва соединения (addAConnection и deleteAConnection, соответственно), а также полный код процедуры изменеY ния соединения (changeAConnection). Последняя проверяет корректность введенного пользователем значения, сохраняет его, после чего вызывает проY цедуры удаления и создания нового соединения. Весь следующий код должен быть помещен в модуль ЭтаКнига (ThisWorkbook). Dim OldVal As String Function addAConnection(Sh As Worksheet, Target As Range) End Function Sub deleteAConnection(Sh As Worksheet, Target As Range, _ OldVal As String) End Sub Sub changeAConnection(Sh As Worksheet, Target As Range, _ OldVal As String) Dim Conn2 As ExcelCoords Dim SavedVal As String Conn2 = WiringToExcelCoords(Target.Value) If Conn2.Row = 0 Then MsgBox Target.Value & " : недопустимое значение" Application.EnableEvents = False Target.Value = OldVal Application.EnableEvents = True Exit Sub '<<<<< End If SavedVal = Target.Value deleteAConnection Sh, Target, OldVal Application.EnableEvents = False Target.Value = SavedVal Application.EnableEvents = True addAConnection Sh, Target End Sub Private Sub Workbook_SheetChange( _ ByVal Sh As Object, ByVal Target As Range) If Not TypeOf Sh Is Worksheet Then Exit Sub If Not (Sh.Range(cWSWiringTagAddr).Value = cWSWiringTagID) _ Then Exit Sub '<<<<< If Target.Cells.Count > 1 Then Практикум: создание приложения Excel “с нуля” Глава 26 MsgBox "Версия 1 этого приложения не поддерживает" & vbNewLine _ & "одновременное изменение нескольких соединений." & vbNewLine _ & "Данное изменение нарушает целостность матрицы." & vbNewLine _ & "Матрица электрических контактов разрушена! " & vbNewLine _ & "Автоматическое изменение матрицы отменено." Exit Sub End If If OldVal = Target.Value Then Exit Sub '<<<<< If Target.Font.ColorIndex = cUnusedColorIdx Then addAConnection Sh, Target ElseIf IsEmpty(Target) Then deleteAConnection Sh, Target, OldVal Else changeAConnection Sh, Target, OldVal End If End Sub Private Sub Workbook_SheetSelectionChange( _ ByVal Sh As Object, ByVal Target As Range) If Not TypeOf Sh Is Worksheet Then Exit Sub If Not (Sh.Range(cWSWiringTagAddr).Value = cWSWiringTagID) _ Then Exit Sub '<<<<< If Target.Cells.Count > 1 Then MsgBox "Версия 1 этого приложения не поддерживает" & vbNewLine _ & "одновременное изменение нескольких соединений." & vbNewLine _ & "Пожалуйста, выделите только одну ячейку." 'Предотвратить рекурсивный вызов процедуры! Target.Cells(1).Select Exit Sub End If OldVal = Target.Value End Sub Этап 3а: нисходящее программирование Разработка приложения практически завершена. Осталось создать код процедур, имеющих непосредственное отношение к инициализации системы, а также код процедур создания и разрыва соединения. Следующие четыре процедуры используются при инициализации системы. Процедура resetFormatting удаляет существующее содержимое раY бочего листа, задает общий формат чисел и светлоYсерый цвет шрифта. Sub resetFormatting(aWS As Worksheet) With aWS.Cells .ClearContents .NumberFormat = "General" .Font.ColorIndex = cUnusedColorIdx End With End Sub Процедура createOneDataBlock использует эквивалент команды главного меню Excel Правка Заполнить Прогрессия (Edit Fill Series) для заполнения значениями от 101 до 700 блока, состоящего из 60 строк и 10 столбцов. 611 612 Часть III Удивительные возможности Visual Basic for Applications Sub createOneDataBlock(aWS As Worksheet) With aWS.Range("A1") .FormulaR1C1 = "101" .DataSeries Rowcol:=xlRows, Type:=xlLinear, _ Date:=xlDay, _ Step:=1, Stop:=110, Trend:=False .Resize(1, 10).DataSeries Rowcol:=xlColumns, _ Type:=xlLinear, Date:=xlDay, _ Step:=10, Stop:=700, Trend:=False End With End Sub Процедура cloneDataBlocks создает 9 копий полученного блока значений nnn, каждый раз определяя его новые координаты. Sub cloneDataBlocks(aWS As Worksheet) Dim Dest As Range, i As Integer With aWS.Range("A1") Set Dest = .Offset(, 10) For i = 2 To 9 Set Dest = Union(Dest, .Offset(, i * 10)) Next i .CurrentRegion.Copy Dest End With End Sub Процедура addMSlash заносит формулу Excel в диапазон, состоящий из 60 строк и 100 столбцов и расположенный непосредственно под суY ществующим содержимым рабочего листа. Формула Excel отображает значения nnn на соответствующие им коды контактных точек m/nnn. После этого процедура addMSlash копирует полученную матрицу на место диапазона значений nnn и удаляет исходный диапазон формул. Sub addMSlash(aWS As Worksheet) With aWS.Range("A1").Offset(60, 0).Resize(60, 100) ' В англоязычной версии Excel: ' .FormulaR1C1 = _ ' "=TRUNC((COLUMN()-1)/10,0)+1 &""/""&R[-60]C" .FormulaR1C1Local = _ "=ОТБР((СТОЛБЕЦ()-1)/10,0)+1 &""/""&R[-60]C" aWS.Cells.NumberFormat = "@" .Copy aWS.Range("a1").PasteSpecial xlPasteValues .EntireRow.Delete End With End Sub Процедура addAConnection создает соединение между указанными конY тактными точками. При возникновении ошибки вызывается процедура resetValue, которая восстанавливает начальное содержимое ячейки рабочего листа Excel. Function addAConnection(Sh As Worksheet, Target As Range) Dim Conn2 As ExcelCoords Conn2 = WiringToExcelCoords(Target.Value) If Conn2.Row = 0 Then MsgBox Target.Value & " : недопустимое значение" Практикум: создание приложения Excel “с нуля” Глава 26 613 addAConnection = True resetValue Target Exit Function '<<<<< End If With Sh.Cells(Conn2.Row, Conn2.Col) If .Font.ColorIndex <> cUnusedColorIdx Then MsgBox Target.Value & " занята!" & vbNewLine _ & "Она соединена с контактной точкой " & _ Cells(Conn2.Row, Conn2.Col).Value resetValue Target addAConnection = True Exit Function '<<<<< End If Application.EnableEvents = False On Error GoTo ErrHandler .Value = XLtoWiringCoords(Target.Row, Target.Column) .Font.ColorIndex = cInUseColorIdx End With Target.Font.ColorIndex = cInUseColorIdx Application.EnableEvents = True Exit Function ErrHandler: MsgBox "Непредвиденная ошибка в функции addAConnection: _ Ячейка=" & Target.Address & vbNewLine _ & "Ошибка=" & Err.Description & " (" & Err.Number & ")" Application.EnableEvents = True End Function Процедура deleteAConnection разрывает соединение между указанныY ми контактными точками путем восстановления начальных значений соответY ствующих ячеек рабочего листа Excel. Sub deleteAConnection(Sh As Worksheet, Target As Range, _ OldVal As String) Dim Conn2 As ExcelCoords Conn2 = WiringToExcelCoords(OldVal) Application.EnableEvents = False On Error GoTo ErrHandler With Conn2 resetValue Sh.Cells(.Row, .Col) End With resetValue Target Application.EnableEvents = True Exit Sub ErrHandler: MsgBox "Непредвиденная ошибка в процедуре _ deleteAConnection: Ячейка=" & Target.Address & vbNewLine _ & "Ошибка=" & Err.Description & " (" & Err.Number & ")" Application.EnableEvents = True End Sub Этап 3б: создание ключевых компонентов Ниже приведен код процедуры resetValue, восстанавливающей начальY ное значение и форматирование ячейки рабочего листа Excel. 614 Часть III Удивительные возможности Visual Basic for Applications Sub resetValue(Target As Range) Application.EnableEvents = False On Error GoTo ErrHandler With Target .Value = XLtoWiringCoords(.Row, .Column) .Font.ColorIndex = cUnusedColorIdx End With Application.EnableEvents = True Exit Sub ErrHandler: MsgBox "Непредвиденная ошибка в процедуре resetValue: _ Ячейка=" & Target.Address & vbNewLine & "Ошибка=" & _ Err.Description & " (" & Err.Number & ")" Application.EnableEvents = True End Sub Резюме В этом практикуме был продемонстрирован процесс разработки реального приложения Excel ‘‘с нуля’’ с использованием различных методик программиY рования: структурного анализа, принципа распараллеливания и принципа нисходящего программирования. Предметный указатель . синтаксис, 60 справочная система, 63 .NET Tools for Office, 31 A ADO (ActiveX Data Objects), 490 объект Connection, 492 Recordset, 492 API (Application Programming Interface). См. Windows API W WebYзапрос обновление, 409 создание, 407; 410 Windows API, 547 объявление, 548 использование, 548 примеры, 549 структура, 548 X C CSV, 429 D XML, 427 правила, 428 сопоставление данных, 430 схема данных, 429 DAO (Data Access Objects), 490 L Lotus 1Y2Y3, 29 А Автозаполнение, 236 Автофильтр, 297 Б M MDB (Multidimensional Database), 489 Microsoft Access, 489 Microsoft Jet, 492 Microsoft Word, 439 S StarOffice, 30 Бриклин, Дэн, 29 В Вкладка, 225 Выражение On Error GoTo, 564 Property Get, 513 Property Let, 513 V VBA. См. Visual Basic for Applications VisiCalc, 29 Visual Basic for Applications, 59 Д Диаграмма, 229 биржевая, 254 616 Предметный указатель встроенная, 204; 230 контейнер, 230 гистограмма, 251 график, 251 иерархическая кольцевая, 265 изменение размещения, 235 кольцевая, 253 коническая, 255 кривой предложения, 264 круговая, 252; 258 круговая пузырьковая, 262 легенда, 247 лепестковая, 253 линейчатая, 251 линии сетки, 245 линия тренда, 248 название, 247 область диаграммы, 231; 237 область построения диаграммы, 240 ось диаграммы, 243 вспомогательная, 245 пирамидальная, 255 поверхностная, 253 подписи данных, 246 полосы погрешности, 248 пузырьковая, 253 расположенная на отдельном листе, 232 ряд данных, 242 с областями, 253 с точками данных в виде спидометров, 264 таблица данных, 247 тип, 251 точечная, 252; 262 трехмерная, 256 цилиндрическая, 254 Диапазон ячеек, 95 именованный, 96 Динамически подключаемая библиотека, 547 Диспетчер объектов, 85 проектов, 46 Документ Word закрытие, 447 открытие, 447 печать, 447 создание, 446 сохранение, 447 И Изображение, 223 Имя глобальное, 177 зарезервированное, 183 локальное, 177 массива, 183 создание, 179 строки, 181 удаление, 180 формулы, 181 числа, 182 К Кейпор, Мич, 29 Кнопка, 220 Код оптимизация, 90 отладка, 561 Коллекция, 60; 63; 515 Areas, 108 Axes, 243 Charts, 233 SeriesCollection, 242 создание в модуле класса, 516 в стандартном модуле, 515 Константа предопределенная, 68 Конструкция Do Until...Loop, 151 Do While...Loop, 150 Do...Loop, 147 For Each...Next, 152 For...Next, 141 If...ElseIf...End If, 157 If...Then...Else, 155 If...Then...Else...End If, 156 If...Then...End If, 156 Select Case...End Select, 157 Type...End Type, 521 Предметный указатель 617 While...Wend, 152 With...End With, 90 Л Личная книга макросов, 42 М Макрос безопасность, 40 выполнение, 43 с помощью гиперссылки, 591 с помощью кнопки, 587 с помощью сочетания клавиш, 587 с помощью элемента управления ActiveX, 590 запись, 42; 48; 53 сохранение, 42 Массив, 463 динамический, 469 заполнение, 464 манипулирование элементами, 466 многомерный, 464 объявление, 463 одномерный, 464 Матрица, 464 Меню пользовательское группирование команд, 578 добавление команд, 577 создание, 575 создание подменю, 579 удаление, 575 Метод, 60; 63 Intersect, 103 Microsoft Word EndKey, 448 HomeKey, 448 TypeText, 448 OpenText, 66 SpecialCells, 106 Union, 103 параметр, 61; 63 Модуль, 46 класса, 47; 505 создание, 506 Н Набор вкладок, 529 Надпись, 220 Надстройка, 593 безопасность, 598 выгрузка, 599 создание, 594 стандартная, 593 удаление, 599 установка, 597 О Объект, 60; 63 ADO Connection, 492 Recordset, 492 ChartArea, 237 ChartObject, 230 Microsoft Word Document, 446 Range, 449 Selection, 448 PlotArea, 241 Range, 95 пользовательский применение, 511 создание, 510 Ограничивающий прямоугольник, 241 Окно Immediate (Быстрое выполнение), 79 Watches (Просмотр), 82 ввода данных, 215 свойств, 47 сообщения, 216 Ошибка времени выполнения ‘‘Method 'Range' of object '_Global' failed’’, 571 ‘‘Subscript out of range’’, 570 игнорирование, 566 извлечение пользы, 568 обработка, 561 выражение On Error GoTo, 564 универсальный обработчик, 566 618 Предметный указатель П Панель инструментов, 222 UserForm, 525 Visual Basic, 39 пользовательская выбор значка кнопки, 584 добавление кнопок, 582 добавление раскрывающегося списка, 584 создание, 582 сохранение и восстановление координат, 585 удаление, 582 Параметр, 61; 63 TrailingMinusNumbers, 68; 478 необязательный, 68 события, 190 Переключатель, 222; 528 Переменная, 89 объектная, 152; 236 цикла, 144 Песочница, 40 Поле ввода, 220 адреса диапазона ячеек, 530 Полоса прокрутки, 539 Пользовательская форма вкладка, 225 вызов, 218 добавление элемента управления, 219 закрытие, 218; 225 изображение, 223 кнопка, 220 модальная, 531 набор вкладок, 529 надпись, 220 немодальная, 531 панель, 222 переключатель, 222; 528 поле ввода, 220 адреса диапазона ячеек, 530 полоса прокрутки, 539 создание, 216 список, 221 комбинированный, 221 счетчик, 224 эффект прозрачности, 545 Примечания ячеек, 376 Р Расширенный фильтр, 267; 364 автофильтр, 297 диапазон условий, 276 Редактор Visual Basic, 45 автозаполнение, 236 диспетчер объектов, 85 диспетчер проектов, 46 окно Immediate (Быстрое выполнение), 79 окно Watches (Просмотр), 82 окно свойств, 47 отладчик, 74 параметры, 45 С Сводная таблица, 299 автоотображение лучшей десятки, 340 вычисляемое поле, 328 вычисляемый элемент, 331 группирование дат, 333 определение размера, 307 перемещение и изменение, 306 создание, 300; 303 удаление, 307 фильтрация данных, 344 Свойство, 62; 63 Cells, 99 Columns, 102 CurrentRegion, 105 Offset, 100 Resize, 101 Rows, 102 Связывание позднее, 442 раннее, 439 Событие, 189 AppEvent_NewWorkbook, 210 AppEvent_SheetActivate, 210 AppEvent_SheetBeforeY DoubleClick, 210 AppEvent_SheetBeforeY RightClick, 210 AppEvent_SheetCalculate, 211 AppEvent_SheetChange, 211 Предметный указатель 619 AppEvent_SheetDeactivate, 211 AppEvent_SheetFollowHyperlink, 211 AppEvent_SheetSelectionChange, 211 AppEvent_WindowActivate, 211 AppEvent_WindowDeactivate, 211 AppEvent_WindowResize, 212 AppEvent_WorkbookActivate, 212 AppEvent_WorkbookAddinInstall, 212 AppEvent_WorkbookAddinUninstall, 212 AppEvent_WorkbookBeforeClose, 213 AppEvent_WorkbookBeforePrint, 213 AppEvent_WorkbookBeforeSave, 213 AppEvent_WorkbookDeactivate, 213 AppEvent_WorkbookNewSheet, 213 AppEvent_WorkbookOpen, 214 Chart_Activate, 205 Chart_BeforeDoubleClick, 205 Chart_BeforeRightClick, 206 Chart_Calculate, 206 Chart_Deactivate, 206 Chart_DragOver, 206 Chart_DragPlot, 206 Chart_MouseDown, 206 Chart_MouseMove, 207 Chart_MouseUp, 207 Chart_Resize, 207 Chart_Select, 207 Chart_SeriesChange, 208 Workbook_Activate, 192 Workbook_AddinInstall, 196 Workbook_AddinUninstall, 196 Workbook_BeforeClose, 194 Workbook_BeforePrint, 193 Workbook_BeforeSave, 193 Workbook_Deactivate, 192 Workbook_NewSheet, 195 Workbook_Open, 192 Workbook_SheetActivate, 197 Workbook_SheetBeforeY DoubleClick, 197 Workbook_SheetBeforeRightClick, 197 Workbook_SheetCalculate, 197 Workbook_SheetChange, 198 Workbook_SheetDeactivate, 198 Workbook_SheetFollowHyperlink, 198 Workbook_SheetSelectionChange, 198 Workbook_WindowActivate, 196 Workbook_WindowDeactivate, 196 Workbook_WindowResize, 195 Worksheet_Activate, 199 Worksheet_BeforeDoubleClick, 199 Worksheet_BeforeRightClick, 200 Worksheet_Calculate, 200 Worksheet_Change, 202 Worksheet_Deactivate, 199 Worksheet_FollowHyperlink, 203 Worksheet_SelectionChange, 203 встроенной диаграммы, 204; 509 листа диаграммы, 204 параметры, 190 приложения, 208; 507 рабочего листа, 199 рабочей книги, 191 Список, 221 комбинированный, 221 Ссылка абсолютная, 52; 167 относительная, 53; 166 смешанная, 167 Стиль записи ссылок A1, 161; 165 R1C1, 162; 165; 166 Счетчик, 224 Т Таблица Access запись добавление, 493 извлечение, 494 обновление, 496 удаление, 499 поле проверка существования, 501 создание, 503 проверка существования, 500 создание, 502 620 Предметный указатель Точка прерывания, 77 создание, 77; 83 удаление, 78 У Условное форматирование ячеек, 171; 364; 381 Ф Файл .dll, 547 .mdb, 489 .xls, 431 .xml, 431 .xsd, 431 текстовый импорт, 473 экспорт, 486 Форма. См. Пользовательская форма Формула A1, 164; 168 R1C1, 164; 168; 169 массива, 174 Фрэнкстон, Боб, 29 Функция CreateObject, 444 GetObject, 444 IsEmpty, 104 определенная пользователем, 111 создание, 112 Ц Цикл, 141 Do Until...Loop, 151 Do While...Loop, 150 Do...Loop, 147 For Each...Next, 152 For...Next, 141 While...Wend, 152 вложение циклов, 146 досрочное завершение, 145 изменение шага, 144 Научнопопулярное издание Билл Джелен, Трейси Сирстад Применение VBA и макросов в Microsoft Excel Литературный редактор Верстка Художественный редактор Корректор П.Н. Мачуга О.В. Линник С.А. Чернокозинский О.В. Мишутина Издательский дом ‘‘Вильямс” 101509, г. Москва, ул. Лесная, д. 43, стр. 1 Подписано в печать 30.09.2005. Формат 70x100/16. Гарнитура Times. Печать офсетная Усл. печ. л. 50,3. Уч.$изд. л. 30,4. Тираж 3000 экз. Заказ № . Отпечатано с диапозитивов в ФГУП ‘‘Печатный двор” Министерства РФ по делам печати, телерадиовещания и средств массовых коммуникаций 197110, С.$Петербург, Чкаловский пр., 15