В начало
Статьи
Библиотека
Разное

Вот здесь - новый сайт, заходите немедленно!

kift - Коллекция Интересных Фактов и Теорий

А тут можно поболтать и побухтеть, милости просим:

kift - неизданное

Введение в программирование на примере VBA

Часть II. Создание макроса-приложения

Аннотация
Предисловие
Часть I. Макрос Word
     1. Проектирование и запись макроса
     2. Разбор макроса
     3. Внесение исправлений
     Итоги
Часть II. Макрос-приложение
     4. Проектирование
     5. Визуальное редактирование
     6. Запуск и остановка
     7. Вывод данных
     8. Выбор ответа
    9. Перемещение по списку
     10. Обратное перемещение
     11. Новая версия
     12. Реализация новой версии
     13. Завершение работы
     Итоги
Часть III. Объект на основе класса
     14. "Основное" приложение
     15. Компонент-таймер
     Итоги
Послесловие

Занятие 9. Перемещение по списку вопросов

Цель занятия – продолжить «наполнение» приложения кодом для придания требуемой функциональности.

Подцикл 5. Переход на следующий вопрос в списке вопросов

Задача этого подцикла – обеспечение перехода на следующий вопрос в списке вопросов. Эта задача очевидно сочетается с обновлением как выводимых вопросов, так и наборов ответов на них. Кроме того, следует предусмотреть случай, когда делается попытка перейти на «следующий» вопрос, находясь на последнем вопросе в наборе.

Коррекция кода процедур чтения данных и стартовой

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

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

Public Sub RecordRead()

With frmTest

.txtQuestion.Text = shtCurrent.Cells(1, 1).Text
.optAnswer1.Caption = shtCurrent.Cells(1, 4).Text
.optAnswer2.Caption = shtCurrent.Cells(2, 4).Text
.optAnswer3.Caption = shtCurrent.Cells(3, 4).Text

End With

End Sub

Взгляните на то, как производится адресация ячеек таблицы Excel, к примеру, как мы находим текст вопроса:

shtCurrent.Cells(1, 1).Text

Синтаксис адреса ячеек, как говорилось выше:

Cells(<ряд>, <колонка>)

Значение <колонка> в нашем примере фиксировано, изменяться от вопроса к вопросу будет значение <ряд>.

Самое место применить переменную для хранения значения <ряд>, назовем ее rw (от rowРяд).

Изменив код таким образом:

.txtQuestion.Text = shtCurrent.Cells(rw, 1).Text
.optAnswer1.Caption = shtCurrent.Cells(rw, 4).Text
.optAnswer2.Caption = shtCurrent.Cells(rw + 1, 4).Text
.optAnswer3.Caption = shtCurrent.Cells(rw + 2, 4).Text

мы сможем прочитать данные любого вопроса, номер вопроса должен быть в переменной rw (пока – такое кодовое обозначение).

Как же передать значение переменной в процедуру? А очень просто, мы уже разбирали, что для этого используются параметры.

  • Видоизменим код процедуры так:

Public Sub RecordRead (intRow As Integer)

With frmTest

.txtQuestion.Text = shtCurrent.Cells(intRow, 1).Text
.optAnswer1.Caption = shtCurrent.Cells(intRow, 4).Text
.optAnswer2.Caption = shtCurrent.Cells(intRow + 1, 4).Text
.optAnswer3.Caption = shtCurrent.Cells(intRow + 2, 4).Text

End With

End Sub

Здесь параметр intRow будет передавать в процедуру номер вопроса, который следует прочитать и предоставить пользователю.

Как хорошо, что мы выделили это действие в отдельную процедуру! Оказывается, вызывать ее придется не только при запуске приложения, но и при переходе на другой вопрос. Тем самым экономится код и упрощается работа.

Но не все так просто, как кажется. Решив одну задачу, мы сталкиваемся с двумя дополнительными – к счастью, они легко решаемы.

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

  • Для этого перейдите в код процедуры StartTest и найдите строку вызова процедуры чтения данных:

Public Sub StartTest()
Set shtCurrent = ActiveWorkbook.Worksheets("Лист1")
Load frmTest
RecordRead
frmTest.Show vbModeless
End Sub

Эта строка в листинге выделена.

  • Поместите курсор после слова RecordRead и нажмите [Space] – то есть, введите пробел.

IDE VBA тут же предоставит подсказку о требуемых параметрах этой процедуры:

Как видите, IDE VBA уже восприняло сделанные вами изменения кода. Следует передать значение аргумента процедуре.

  • Измените код так:

Public Sub StartTest()
Set shtCurrent = ActiveWorkbook.Worksheets("Лист1")
Load frmTest
RecordRead 1
frmTest.Show vbModeless
End Sub

Да, мы всего лишь добавили единицу после слова RecordRead – но ведь и требуется чтение данных первого вопроса, не так ли?

  • Сохраните изменения и проверьте, правильно ли стартует программа.
  • Сделайте пробный запуск.

Не должно быть отличий от предыдущих запусков.

  • Остановите приложение. Не забывайте комментировать код!

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

Теперь реализуем переход на следующий вопрос по нажатию кнопки cmdNext.

Уже формулировка задачи отсылает нас к обработчику события Click для этой кнопки.

Уточним задание: при нажатии этой кнопки должен происходить переход на следующий вопрос и обновление списка ответов.

Иными словами, мы будем производить вызов процедуры RecordRead с другим значением параметра. А именно – значение будет на 1 больше, чем в предыдущий раз.

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

Для этого используем переменную. Так как она должна существовать долго, объявим ее там же, где мы объявляли переменную для хранения ссылки на лист Excel.

  • Перейдите в начало модуля basShell.
  • Внесите строку:

Public intCurrentRecord As Integer

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

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

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

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

  • Перейдите в процедуру StartTest, измените ее код так:

Public Sub StartTest()
Set shtCurrent = ActiveWorkbook.Worksheets("Лист1")
intCurrentRecord = 1
Load frmTest
RecordRead intCurrentRecord
frmTest.Show vbModeless
End Sub

В листинге выделены измененные строки кода.

Как видите, в начале процедуры мы назначаем переменной значение. А первый вызов процедуры RecordRead происходит не с явным указанием номера вопроса, а с использованием переменной. Хотя можно оставить и как было:

RecordRead 1

Тогда вы создадите себе лишний повод для путаницы – если переменная intCurrentRecord предназначена для хранения номера записи, но она не передается RecordRead, то для чего же она? Такое затруднение обязательно будет возникать. Поэтому давайте делать все строго и последовательно.

Уже можно приступать к обработчику события щелчка на кнопку.

  • Откройте окно кода формы frmTest и перейдите в обработчик события cmdNext_Click:

Private Sub cmdNext_Click()
MsgBox AnswSelected
End Sub

Да-да, мы недавно работали с этим обработчиком, проверяя работу переключателей.

  • Оставим пока тестовый вызов MsgBox и добавим код:

Private Sub cmdNext_Click()
MsgBox AnswSelected

intCurrentRecord = intCurrentRecord + 1
RecordRead intCurrentRecord
End Sub

Как видите, добавлены две строки.

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

  • Сохраните изменения и сделайте пробный запуск.
  • Щелкните на кнопке cmdNext.

Сперва появится окошко сообщения (если вы не выбрали переключатель, на нем будет 0), а затем – список ответов изменится, а текст вопроса вообще исчезнет.

Отладка. Пересчет номера вопроса в номер строки

И вот – вторая проблема, о которой вы были предупреждены недавно.

Мы с вами допустили ошибку, предположив, что достаточно будет перейти в хранилище данных на одну строку, тогда как надо перейти на один вопрос. Если взглянуть на структуру данных таблицы Excel, будет понятно, что переход на один вопрос равносилен переходу на 3 строки. Именно для того, чтобы научить вас справляться с такими ситуациями, структура данных была оставлена как есть. Проще разместить все данные в одну строку – тогда данной проблемы не было бы, но вы не получили бы урок.

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

Применим константу.

Константа – именованное хранилище информации, содержимое которого можно считывать при выполнении программы.

Изменять значение константы во время выполнения программы нельзя – этого не позволит сам язык VBA. Значение константе присваивается при создании программы.

Синтаксис объявления константы схож с объявлением переменной:

Const <имя_константы> [As <тип_константы>] = <значение>

Квадратные скобки здесь обозначают необязательность указания типа. Если тип не указан, VBA сам решит, какой тип наиболее подходит для этого <значения> – а мы уже знаем, что его догадки не всегда хороши.

К константе также можно применять квалификаторы Public- Private. Если квалификатор не указан, то константа приватная.

  • Перейдите в окно кода модуля basShell.
  • Переместитесь в раздел глобальных объявлений – в самое начало модуля.
  • Внесите строку:

Public Const LineCount = 3

Как видите, префикс int мы не использовали. Пусть это отличает константу от переменной. Еще раз напомним, что имена для объектов VBA выбираются произвольно, лишь бы они отвечали общим правилам именования.

Посмотрите, как это будет выглядеть:

  • Если вы хотите указать тип константы, напишите так:

Public Const LineCount As Integer = 3

  • А теперь изменим процедуру RecordRead.

В ней как параметр мы принимаем номер ряда, но передаем-то номер записи!

  • Сделаем пересчет.

Public Sub RecordRead(intRec As Integer)

Dim intRow As Integer

intRow = ((intRec – 1) * LineCount) + 1

With frmTest

.txtQuestion.Text = shtCurrent.Cells(intRow, 1).Text

.optAnswer1.Caption = shtCurrent.Cells(intRow, 4).Text

.optAnswer2.Caption = shtCurrent.Cells(intRow + 1, 4).Text

.optAnswer3.Caption = shtCurrent.Cells(intRow + 2, 4).Text

End With

Строку расчета значения переменной intRow проанализируйте сами. Порядок выполнения подобных сложных формул – такой же, как в обычной алгебре, подробнее – в справке VBA.

  • Сохраните проект и сделайте пробный запуск.
  • Щелкните на кнопке cmdNext.

Должно появиться окошко сообщения.

  • Щелкните OK на этом окошечке.

Если все сделано верно – вы увидите, что текст вопроса изменился. Другим стал и список ответов.

  • Повторите эти действия несколько раз, чтобы удостовериться в том, что ошибки нет.
Отладка. Достижение конца набора вопросов

Все происходит хорошо, пока мы движемся по списку вопросов. Но когда текущим является последний – у нас четвертый, – вопрос, и мы переходим по нажатию cmdNext, то переход происходит «в никуда».

Вывод – следует определять, не дошли ли мы до последнего вопроса, и, если так, – предотвращать переход «в никуда».

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

Один из способов – задать заранее количество вопросов в списке и заложить эту величину в программу как константу. Недостаток этого способа очевиден – изменение содержимого теста будет затруднительно.

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

Определить число вопросов не так уж сложно. Для этого следует определить общее число строк в таблице Excel, а затем разделить это число на количество строк в одном вопросе. А количество строк в вопросе уже задано в программе.

Определение числа строк таблицы Excel

Проблема сводится к определению числа значимых строк в таблице Excel. Уже упоминалось, что нет какого-то указателя конца таблицы.

Поищем ответ в справке VBA.

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

  • Откройте окно кода модуля basShell.
  • Поместите курсор щелчком на слове ActiveWorkbook в процедуре StartTest (или на любом другом слове языка в окне кода – но не на имени, назначенном вами).
  • Нажмите [F1].

Откроется справка, относящаяся к программированию на VBA.

Если вы заметили, нажатие [F1] тогда, когда вы работаете с Excel, приводит к открытию совершенно другого раздела, относящегося к Excel, но не VBA.

Как помните, объект, обозначающий ячейки, ряды и колонки – Range.

  • Произведите поиск по слову Range.
  • В результатах поиска выберите пункт Range ObjectОбъект Range.
  • Ознакомьтесь со списком свойств этого объекта.

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

Как видите, у объекта Range есть свойство RowsРяды.

  • Перейдите на страницу справки, описывающее это свойство.

Это свойство представляет ряды у «родительского» объекта, но имеет значение Range. Иными словами, Rows – набор, один член которого – не Row, как можно было подумать, а Range. С подобным мы уже сталкивались раньше.

Подсказка – наборы VBA, как правило, сами являются объектами типа Collection  – Коллекция. А эти объекты имеют, помимо всего прочего, свойство CountКоличество, Счет, – хранящее число членов набора.

У нас уже вырисовывается конструкция:

...Rows.Count

Какой объект Range возьмем за основу для вычисления числа рядов?

Напрашивается, что этот Range должен как-то относиться к текущему листу Excel. В нашем приложении он уже назначен переменной shtCurrent.

Далее подскажем решение. Оно не вполне очевидно.

У объекта Range, оказывается, есть свойство CurrentRegion(ТекущийРегион), обозначающее «объект Range, ограниченный любой комбинацией пустых рядов и колонок» (перевод справки). Тип этого свойства – все тот же Range.

То, что надо.

Итак, свойство CurrentRange определит регион, ограниченный пустыми рядами и колонками.

Нам следует сначала «оказаться» в этом регионе. Для этого принудительно сделаем переход в верхнюю левую ячейку листа – а там у нас есть данные, текст первого вопроса. Свойство CurrentRange будет «расширяться» до тех пор, пока не обнаружит, что данных вокруг больше нет.

Это будет выглядеть так:

shtCurrent.Range(“A1”).CurrentRegion.Rows.Count

Обратите внимание на адресацию левой верхней ячейки. Это – один из существующих в Excel способов адресации, состоящий в указании индекса колонки и ряда.

Эта конструкция неочевидна. Поиск решения подобных задач может занять много времени – таков, к сожалению, недостаток справочной системы продуктов Microsoft.

Здесь нельзя применить цикл Do...Loop – действия производятся не самостоятельно, а в ответ на нажатие кнопки cmdNext. Поэтому стратегия проверки наступления условия будет другой.

Для этого используем концепцию флага.

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

А в начале процедуры происходит проверка значения флага.

Давайте сформулируем более подробно.

Конструкция будет выглядеть примерно так (здесь fl – флаг).

Если fl = True, То
<действия_1>
Выход из процедуры
Иначе (Если fl = False), То
<действия_2>
Если (дошли до конца набора), То fl = True

Разберем этот шаблон подробно.

Сначала проверяем флаг fl. Если fl = True (иначе говоря, если флаг установлен), то выполняем <действия_1>, предназначенные для исполнения по достижении конца набора, а затем – выходим из процедуры. Дальнейшие действия не выполняем.

Если флаг fl = False (то есть, если флаг сброшен), проделываем <действия_2>, предназначенные для обычного случая, затем проверяем достижение конца набора, и, если находимся на последнем элементе набора, то устанавливаем флаг fl (делаем флаг равным True). Значение флага будет проверяться при следующем выполнении этого блока.

Запишем этот блок «псевдокодом», похожим на VBA:

If fl = True Then
<действие_1>
Else(If fl = False Then)
intCurrentRecord = intCurrentRecord + 1’действие_2
RecordRead intCurrentRecord

If intCurrentRecord = shtCurrent.Range(“A1”).CurrentRegion.Rows.Count / LineCount Then
fl = True
End If
End If

Обратите внимание на громоздкую конструкцию

shtCurrent.Range(“A1”).CurrentRegion.Rows.Count / LineCount

Делимое здесь – общее число строк в таблице Excel, делитель – число строк, относящихся к одному вопросу. Результат вычисления – общее число вопросов.

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

В глобальном разделе помещаем объявление:

Public intQuestions As Integer

В процедуре StartTest вычисляем:

intQuestions = shtCurrent.Range(“A1”).CurrentRegion.Rows.Count / LineCount

А в блоке проверки помещаем результат:

If fl = True Then
<действие_1>
Else(If fl = False Then)
intCurrentRecord = intCurrentRecord + 1

   RecordRead intCurrentRecord

If intCurrentRecord = intQuestions Then
fl = True
End If
End If

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

If intCurrentRecord = intQuestions Then
<действие_1>
Else (intCurrentRecord < intQuestions Then)
intCurrentRecord = intCurrentRecord + 1
RecordRead intCurrentRecord
End If

При этом блок заметно упрощается.

Но во многих других случаях такая непосредственная проверка невозможна или чрезвычайно затруднительна, и следует  применить флаг. Поэтому воспользуемся «флаговым» вариантом – в учебных целях. Кроме того, вспомните, что впереди – обработка обратного движения по набору вопросов (кнопка cmdPrev), где флаг может понадобиться.

Проектирование деятельности, выполняемой при достижении конца набора вопросов

Давайте определимся с <действием_1>, производимым в конце набора вопросов, то есть, по сути дела, в конце теста.

Очевидно, что переход на следующую запись не нужен – некуда переходить. Здесь целесообразно выполнить обработку результатов теста, показать их пользователю, и, или предложить повторное тестирование, – или завершить работу.

Обработку результатов теста выполним в виде упрощенной процедуры, примерно так:

Sub Summary ()
     MsgBox “Конец теста”
End Sub

Вы уже должны понимать, что произойдет при выполнении этой процедуры.

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

Реализация обработчика достижения конца набора вопросов

Оформим код:

If fl = True Then
Summary
Else(If fl = False Then)
intCurrentRecord = intCurrentRecord + 1’действие_2
RecordRead intCurrentRecord

If intCurrentRecord = intQuestions Then
fl = True
End If
End If

В принципе, можно приступать к реализации. Но мы почти забыли одно упущение, сделанное нами (специально, конечно) гораздо раньше, при визуальном конструировании приложения.

А именно – мы еще не определились с надписью на кнопке cmdNext.

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

cmdNext.Caption = “Конец теста”

Но мы сделаем немного по-другому. Этот способ на первый взгляд более трудоемок, но вы почувствуете его преимущества, если потребуется быстро изменить надписи в приложении – к примеру, перевести приложение на английский.

Мы воспользуемся константами, содержащими строки-надписи. В области глобальных определений модуля создадим несколько констант, содержащих нужные надписи. А именно: основная надпись на cmdNext, дополнительная надпись на кнопке cmdNext и надпись на кнопке cmdPrev.

Это будет выглядеть так:

Public Const captNext As String = “ >>”
Public Const captPrev As String = “ <<”
Public Const captFinish As String = “Конец”

Как видите, тип константы – String. Этот тип может содержать текстовые строки. За более подробной информацией обращайтесь к справке VBA.

Теперь давайте реализуем все, о чем говорилось выше.

  • В области глобальных объявлений модуля basShell добавьте объявления:

Public intQuestions As Integer
Public fEnd As Boolean
Public Const captNext As String = ">>"
Public Const captPrev As String = "<<"
Public Const captFinish As String = "Конец"

Как видите, флаг мы переименовали в fEnd – от слова EndКонец. Пусть имена будут значимыми.

Процедура StartTest приобретет вид:

Public Sub StartTest()
Set shtCurrent = ActiveWorkbook.Worksheets("Лист1")
intCurrentRecord = 1
fEnd = False
intQuestions = shtCurrent.Range("A1").CurrentRegion.Rows.Count / LineCount
Load frmTest
RecordRead intCurrentRecord
frmTest.Show vbModeless
End Sub

Процедуру Summary можно поместить в basShell, а можно специально для нее создать новый модуль. Это следует сделать, если ожидается объемная и сложная обработка результатов, или если предполагается использовать эту процедуру в других проектах. Также рекомендуется вынести обработку результатов в отдельный модуль, если обработчик «не уместится» в одной процедуре, и придется создавать вспомогательные подпрограммы.

В общем случае обработчик надо вынести в отдельный модуль.

Создание модуля для обработчика результатов теста

Добавим к проекту модуль:

  • В окне Project Explorer выполните правый щелчок мыши.
  • В контекстном меню выполните команду InsertModule.

Должно открыться пустое окно кода нового модуля.

  • Если почему-то этого не произошло, откройте его сами.
  • В окне Properties измените имя нового модуля на basSumm.
  • Внесите в окно кода текст процедуры:

Public Sub Summary()
MsgBox "Конец теста"
End Sub

  • Перейдем в окно кода формы frmTest.
  • Измените обработчик события нажатия на кнопку cmdNext так:

Private Sub cmdNext_Click()
MsgBox AnswSelected
If fEnd = True Then
Summary
Else
intCurrentRecord = intCurrentRecord + 1
RecordRead intCurrentRecord
If intCurrentRecord = intQuestions Then
fEnd = True
cmdNext.Caption = captFinish
End If
End If
End Sub

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

Первичное назначение надписей кнопкам

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

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

В справке VBA написано, что событие InitializeИнициализация – происходит после загрузки формы, но до ее вывода на экран. Это – самый подходящий момент для назначения надписей.

  • В левом списке окна кода формы frmTest выберите пункт UserForm – так обозначается форма «изнутри».

Автоматически возникнет заготовка обработчика события Click – она нам не нужна.

  • В правом списке окна кода найдите строку Initialize.

Должна появиться заготовка обработчика этого события.

  • После этого можете удалить ненужную заготовку обработчика события UserForm_Click.
  • Внесите код в заготовку, чтобы получилось так:

Private Sub UserForm_Initialize()
cmdNext.Caption = captNext
cmdPrev.Caption = captPrev
End Sub

  • Сохраните проект.
  • Сделайте пробный запуск.

Как видите, перемещение происходит нормально, но надпись на кнопке не меняется и окончания теста не происходит. Где-то ошибка.

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

Отладка

Появление в создаваемой программе ошибок – нормальное явление, человек не может не ошибаться, как бы этого ни хотелось. VBA имеет некоторые средства для облегчения поиска и исправления ошибок в тексте приложения.

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

Как вы уже знаете, VBA допускает работу IDE VBA в режиме создания программы, когда вы пишете код или конструируете визуальную часть (design-time, время создания)  и в режиме работы создаваемого приложения (run-time, время исполнения).

Оказывается, у IDE VBA имеется еще один режим работы, применяемый для «пошагового» выполнения кода. Именно этот режим используется при отладке.

Перейти в пошаговый режим можно по-разному.

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

Во-вторых, можно сразу запустить программу таким образом.

  • Для выполнения программы в пошаговом режиме нажмите [F8] (в отличие от обычного выполнения, когда запуск производился [F5]).

Не забудьте поместить текстовый курсор в код процедуры StartTest – с этой процедуры начинается работа приложения.

Вы увидите, что первая строка кода изменит цвет, а на вертикальной полоске слева появится значок:

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

  • Нажмите [F8] еще раз.

Значок-стрелка перейдет на следующую строку и цветовая «подсветка» – также.

Таким образом вы можете «прошагать» всю программу, хотя это может оказаться очень долго.

Остановка пошагового режима – по нажатию кнопки с «квадратиком», как и для обычного выполнения.

Из пошагового режима вы можете в любой момент перейти в режим обычного выполнения, для этого в очередной раз нажмите не [F8], а [F5].

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

Точка останова (BreakPoint) – особого рода метка, создаваемая IDE VBA в коде. При выполнении программы в обычном режиме, по достижении точки останова происходит приостановка программы. Дальнейшее продолжение выполнения возможно как в обычном, так и в пошаговом режиме.

Какой же смысл в пошаговом проходе? Дело в том, что IDE VBA предоставляет некоторые интересные и полезные возможности именно для отладки приложений.

Давайте попробуем.

Начните пошаговый проход процедуры StartTest, если вы еще не сделали этого.

  • Поместите текстовый курсор в код процедуры (единичным щелчком мыши).
  • Нажмите [F8].

Должна выделиться строка-заголовок процедуры:

Public Sub StartTest()

  • Нажмите [F8] еще раз.

Выделение перейдет на следующую строку:

Set shtCurrent = ActiveWorkbook.Worksheets("Лист1")

  • Теперь поместите курсор мыши на слово shtCurrent (нажимать кнопки мыши не надо).

Появится подсказка:

Вы видите очень любопытный текст:

shtCurrent = Nothing

Слово NothingНичто, – в VBA обозначает, что объектной переменной не назначено значение. Иными словами, всплывающая подсказка в таком режиме отображает значения переменных!

  • Еще раз нажмите [F8].

Подсветка перейдет на следующую строку.

Теперь подсказка над словом shtCurrent не появится вовсе. Действительно, значение объектной переменной слишком сложно для отображения в маленькой табличке.

Но вы можете проследить изменение значений других переменных по мере перемещения «подсветки».

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

На проверку напрашивается конструкция

shtCurrent.Range("A1").CurrentRegion.Rows.Count

Эта строка неочевидна, создание ее было сопряжено с интуитивными догадками, и справочная система VBA не дала внятной информации на эту тему… Давайте проверим именно этот код.

Создадим на этой строке точку останова.

Создание точки останова

Для этого:

  • Перейдите в код процедуры StartTest, в которой встречается данная строка.
  • Найдите строку с этим кодом.
  • Один раз щелкните на вертикальной полосе слева напротив нужной строки:

На полосе слева должен появиться «кружочек», а строка изменит цвет.

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

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

Теперь давайте запустим приложение.

  • Нажмите [F5].

Программа запустится, дойдет до нужной строки и остановится. Строка с точкой останова изменит цвет.

Давайте проверим значение конструкции

shtCurrent.Range("A1").CurrentRegion.Rows.Count

  • Наведите курсор на эту часть строки кода:

А вот это интересно. VBA определил, что число заполненных рядов в таблице Excel равно 1.

  • Нажмите [F8] для перехода на другую строку – быть может, что-то изменится?
  • Вновь проверьте значение данной конструкции.

Изменений не произошло.

Видимо, мы нашли ошибку.

  • Остановите выполнение приложения.

Ошибка в структуре данных

Давайте проанализируем возможную причину ошибки.

Строка кода составлена по всем правилам, скажем заранее – здесь ошибок нет.

  • Взгляните на таблицу Excel, в которой хранятся данные.

Третья колонка, в которой мы собирались помещать значения выбранных ответов, пуста!

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

Получается, что данная строка кода обрабатывает не всю таблицу данных, а только небольшой ее участок:

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

Подобные ошибки, выявленные при создании больших и сложных программ, могут дорого стоить. Избежать их можно – внимательным проектированием, хорошим знанием объектных библиотек и разбиением процесса разработки на множество маленьких «цикликов» – тогда неудача одного из них не сильно затормозит работу. Но в данном случае ошибка была допущена специально и исправить ее легко.

Как вы помните, третий столбец таблицы изначально предназначался нами для помещения полученных в результате теста ответов. Мы пока не заполнили этот столбец – и поплатились за лень.

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

  • Сделайте соответствующие изменения в таблице Excel.
  • Запустите приложение.

Проверьте значение нужной конструкции:

Как видите, теперь определение числа рядов происходит правильно.

  • Остановите выполнение.
  • Удалите точку останова щелчком по «кружочку».

Удалить сразу все точки останова во всем проекте можно, нажав [Ctrl]+[Shift]+[F9]. Это удобно для того, чтобы не просматривать весь текст, который может быть очень большим.

  • Вновь запустите приложение, на этот раз – в обычном, не отладочном режиме.
  • Несколько раз нажмите кнопку cmdNext (не забудьте, сейчас на ней надпись « >>»).

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

Наша цель достигнута.

  • Остановите приложение.
  • Внимательно и подробно прокомментируйте весь код, созданный или измененный нами.
  • Сохраните результаты работы.
Доработка документации проекта

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

Давайте сделаем схемы, хотя бы и по окончании работы. Быть может, они пригодятся потом.

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

Рядом с обозначением проверки условий следует указать, какое именно условие проверяется.

Стрелочки, исходящие из ромбика, указывают на цепочку, выполняемую в случае выполнения условия – стрелка с надписью [да] и в случае, когда условие не выполняется – стрелка с надписью  [нет].

Если цепочки [да] и [нет] объединяются, используется обозначение:

  • Измените схему процедуры чтения данных из таблицы – а еще лучше, создайте схему заново:

  • Теперь схематизируем процесс перехода от вопроса к вопросу.

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

  • Наконец, формализуем процесс запуска приложения. Ничего сложного здесь нет, но схема пригодится:

На этом завершим занятие.

Итог занятия

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

Как вы, наверное, заметили, помимо создания нового кода, мы вносили исправления в уже имевшийся. Это нормально для итерационного процесса разработки. На предыдущих стадиях вы создали работоспособное приложение – но для расширения функциональности пришлось его переработать. Если бы мы составляли код так, чтобы не изменять его в дальнейшем, то ранние версии приложения не смогли бы работать, а тогда вы не имели бы стимула трудиться, накапливались бы ошибки, которые легко «отловить» при итерационной технологии, и был бы недоволен потенциальный заказчик… Без шуток, почему бы и не продавать свои программы? Плох тот солдат, и так далее...

Важная иллюстрация, которую вы получили в ходе занятия – создание процедуры с параметром. Более того, эта процедура – модификация написанной ранее процедуры без параметров, что должно сделать нагляднее разницу между ними. Также вы увидели два способа вызова такой процедуры – с передачей абсолютного значения и с аргументом-переменной.

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

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

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

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

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

Если примитивные синтаксические ошибки VBA заставляет исправлять при наборе кода – вы, конечно, уже сталкивались с окошком-предупреждением, появляющимся при неверном наборе слова, то ошибки, допущенные при проектировании найти гораздо сложнее. Очень неприятная ошибка, продемонстрированная в этом занятии, связана с неверным составлением правильно спроектированных данных, в реальности ее поиск мог бы занять не один день… Будьте внимательны!

И конспект:

  • Константы, определение, синтаксис.

  • Отличие констант от переменных.

Hosted by uCoz