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

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

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

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

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

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

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

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

Занятие 12. Реализация новой версии

Цель занятия – реализовать функциональность, спроектированную нами перед этим. Это занятие будет включать несколько подциклов – так как они небольшие, не будем мелочиться и объединим несколько задач в одно занятие.

Подцикл 2. Чтение данных из таблицы Excel во временное хранилище

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

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

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

Один из принципов проектирования, который следует по возможности соблюдать – каждый компонент должен выполнять какую-то одну деятельность, не надо «смешивать» разные виды деятельности в одном компоненте.

Поэтому есть смысл вынести деятельность по считыванию данных в отдельную процедуру.

Проектирование последовательности действий

Теперь следует определить последовательность действий по чтению данных из основного хранилища.

  • Схематизируем эту последовательность:

Подобная схема – цикл со счетчиком (синтаксис мы рассматривали раньше). Единственное замечание – как правило, счетчик делается равным 0 и затем увеличивается на 1 до достижения заданного значения. Разницы нет, это – дело выбора.

  • Изменим схему так, чтобы счетчик увеличивался:

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

Цикл For…Next

  • Сформулируем схему цикла в «псевдокоде» VBA.

For <index>=1 To intQuestions
     <действия>
Next <index>

Не правда ли, схема была гораздо сложнее…

Здесь <index> – произвольное имя переменной.

Хотя это и не указано в справке VBA, но тип индекса цикла – Integer, а, значит, цикл не может иметь более 32768 проходов (если начать с нуля до 32767). Но и размер массива-хранилища ограничен тем же размером. Да и число строк в таблице Excel, оказывается, не может быть больше этого числа – так что для наших целей хватит.

Решим, какие действия мы будем выполнять в цикле.

Действия каждого прохода цикла

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

Это будет делаться сходно с тем, что выполнялось в процедуре RecordRead:

intRow = ((<index> – 1) * LineCount) + 1
arStore(i).Well = shtCurrent.Cells(intRow, 2)
arStore(i).Answ = shtCurrent.Cells(intRow, 3)

Вначале рассчитываем значение вспомогательной переменной intRow – также и для того же, что и в процедуре RecordRead, – значения, с которыми будем работать, находятся не в каждой строке таблицы, а через каждые LineCount строк. Значение же LineCount равно числу ответов на один вопрос.

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

  • Объединим структуру цикла и «полезный» код:

For i = 1 To intQuestions
intRow = ((i – 1) * LineCount) + 1
arStore(i).Well = shtCurrent.Cells(intRow, 2)
arStore(i).Answ = shtCurrent.Cells(intRow, 3)
Next i

Реализация – процедура чтения данных

Достаточно добавить объявление переменной intRow и можно пользоваться:

Public Sub SheetRead()

Dim intRow As Integer

With shtCurrent

For i = 1 To intQuestions
intRow = ((i – 1) * LineCount) + 1
arStore(i).Well = .Cells(intRow, 2)
arStore(i).Answ = .Cells(intRow, 3)
arStore(i).Flag = False
Next i

End With

End Sub

Как видите, добавлена строка установки флага в False.

Хотя VBA при создании самостоятельно присваивает логическим переменным значение False, эта строка – для надежности и уверенности в результате. Не следует доверять тому, что VBA делает самостоятельно.

Кроме того, добавлена конструкция With – для «облегчения» кода.

  • Поместите код процедуры в модуль basShell.

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

  • Следует поместить строку вызова новой процедуры в StartTest:

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

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

Сообщений об ошибках не должно быть.

Отладка – код для проверки правильности выполнения

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

  • Давайте поместим временный код в процедуру, вызываемую при каждом переходе от вопроса к вопросу (не забудьте потом удалить его!).

А именно, в процедуру 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
MsgBox "Вопрос № " & intRec & vbCrLf & "правильный ответ № " & _

    arStore(intRec).Well & vbCrLf & _

    "полученный ответ № " & arStore(intRec).Answ
End Sub

  • Запустите и проверьте работу.

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

Работа со строками VBA

Как видите, вызывается MsgBox с каким-то странным аргументом. В нем перемежаются строки текста, переменные и какие-то символы.

Здесь происходит динамическое формирование строки, которая будет выведена функцией MsgBox.

Символ & обозначает, что строки слева и справа от него «складываются», формируя одну строку, это можно пояснить схемой:

<строка_1> & <строка_2> = <строка_1строка_2>

VBA сам переводит значения переменных в строки для построения результирующей строки (например, переменная intRec, встречающаяся в коде – типа Integer, VBA незаметно для программиста переводит ее в тип StringСтрока, чтобы был верный результат).

Переменная intRec в коде – номер записи (то есть номер вопроса теста). «Ячейки» массива-хранилища определяются именно согласно номерам вопросов, что закономерно.

Строка vbCrLfслужебная константа VBA, при появлении ее в данном месте будет перевод строки.

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

<строка_1> _
<строка_2>

То есть в конце первой подстроки помещается пробел, потом – знак подчеркивания, а затем – строка переводится (нажатием [Enter]). Имейте в виду, что разбивать таким способом можно только в местах пробелов в коде, то есть имена переменных, объектные структуры и подобные конструкции не переносятся, при попытке такого переноса VBA сообщит об ошибке. IDE VBA позволяет переносить одну строку до 16 раз.

Заключительная стадия – комментирование кода.

  • Сделайте это.

Подцикл 3. Занесение полученного ответа во временное хранилище

Мы справились с чтением данных их таблицы Excel во временное хранилище.

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

Проектирование

Взаимодействие объектов

Занесение ответа в хранилище сделать просто. При завершении работы с текущим вопросом (при нажатии кнопки перехода) ячейке хранилища, соответствующей данному вопросу, следует присвоить значение выбранного ответа. Следует иметь в виду, что переход может быть как вперед по набору вопросов, так и назад, видимо, будут задействованы обработчики событий Click кнопок cmdNext и cmdPrev.

Псевдокод будет таким:

<при переходе>
arStore(<текущий вопрос>).Answ = <полученный ответ>

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

  • Оформим строку надлежащим образом:

arStore(intCurrentRecord).Answ = AnswSelected

  • Поместите эту строку в обработчик события Click кнопок cmdNext и cmdPrev.

Private Sub cmdNext_Click()
If fEnd = True Then
Summary
Else
fStart = False
cmdPrev.Enabled = True
    arStore(intCurrentRecord).Answ = AnswSelected
    intCurrentRecord = intCurrentRecord + 1
RecordRead intCurrentRecord
If intCurrentRecord = intQuestions Then
fEnd = True
cmdNext.Caption = captFinish
End If
End If
End Sub

Private Sub cmdPrev_Click ()
arStore(intCurrentRecord).Answ = AnswSelected
intCurrentRecord = intCurrentRecord – 1
fEnd = False
cmdNext.Caption = captNext

If intCurrentRecord = 1 Then
fStart = True
cmdPrev.Enabled = False
End If

RecordRead intCurrentRecord

End Sub

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

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

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

Шаблоны проектирования

Удобно делать это, отслеживая нажатие (Click) на переключателе. Независимо от того, было изменено значение переключателя или нет, щелчок на нем обозначает ответ на вопрос.

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

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

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

Для нынешней ситуации определение шаблона будет таким:

Некую обязанность выполняет тот объект, который имеет «знания» и «возможности» для выполнения.

Название подобного шаблона, общепринятое у проектировщиков – Эксперт.

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

Заметим, что все, сделанное нами ранее, также соответствовало тем или иным шаблонам проектирования.

Но не впадайте в уныние из-за того, что вам пока неизвестны шаблоны.

Основное положение, лежащее в основе шаблонов – интуитивная понятность, очевидность и практичность.

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

Исходя из вышесказанного, очень просто составить псевдокод, предназначенный для определения отвеченного вопроса:

<переключатель>_Click
arStore(<текущая позиция>).Flag = True

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

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

Реализация
  • Изменим код программы:

Private Sub optAnswer1_Click()
intAnswer = 1
    arStore(intCurrentRecord).Flag = True
End Sub

Private Sub optAnswer2_Click()
intAnswer = 2
    arStore(intCurrentRecord).Flag = True
End Sub

Private Sub optAnswer3_Click()
intAnswer = 3
    arStore(intCurrentRecord).Flag = True
End Sub

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

  • Сохраните код.
  • Внесите комментарии. Измените диаграммы, построенные ранее.

Подцикл 4. Дополнение кода перехода по списку вопросов

Во время данного подцикла перед нами две подзадачи:

  • учет «отвеченности» вопроса и запрещение перехода вперед, если текущий вопрос не отвечен;
  • восстановление значений переключателей соответственно полученным ранее ответам.

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

Займемся первой подзадачей.

Подподцикл 1. Запрещение продвижения вперед

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

Проектирование

  • Составим схему последовательности действий согласно этому описанию.

Вот схема блокирования кнопки cmdNext:

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

  • А вот так будет происходить проверка получения ответа на вопрос и разблокирование cmdNext:

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

  • Схема тривиальна и вы нарисуете ее самостоятельно.

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

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

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

А разблокирование cmdNext при переходе назад очевидно поместить в обработчик события нажатия cmdPrev.

Реализация

Сложнее всего реализовать первый участок кода.

  • Поэтому сначала оформим псевдокод:

<нажатие cmdNext>
‘уже после перехода на следующий вопрос,
‘при этом следующий вопрос становится текущим
If <текущий вопрос> = <отвечался> Then
     cmdNext = <разблокирована>
Else
     cmdNext = <заблокирована>  
End If

За блокирование кнопок (и других элементов управления) отвечает свойство Enabled (Разрешено).

  • Псевдокод приобретает вид:

<нажатие cmdNext>
‘уже после перехода на следующий вопрос,
‘при этом следующий вопрос становится текущим
If <текущий вопрос> = <отвечался> Then
     cmdNext.Enabled = True
Else
     cmdNext.Enabled = False    
End If

Чтобы определить, «отвечался» ли текущий вопрос, следует проверить значение соответствующего флага в хранилище.

Определение, отвечался ли вопрос

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

intCurrentRecord = intCurrentRecord + 1

и новый код следует поместить после нее.

<нажатие cmdNext>
.
.
intCurrentRecord = intCurrentRecord + 1
.
.
If <текущий вопрос> = <отвечался> Then
     cmdNext.Enabled = True
Else
     cmdNext.Enabled = False    
End If

Тогда условие в блоке If...Then приобретет вид:

arStore(intCurrentRecord).Flag = True

Здесь мы проверяем, установлен ли в True флаг, соответствующий текущему вопросу.

  • Псевдокод конкретизируется:

<нажатие cmdNext>
.
.
intCurrentRecord = intCurrentRecord + 1
.
.
If arStore(intCurrentRecord).Flag = True Then
     cmdNext.Enabled = True
Else
     cmdNext.Enabled = False    
End If

Уже можно вносить коррективы в проект.

  • Перейдите в код формы frmTest, в обработчик события cmdNext_Click и добавьте строки:

cmdPrev.Enabled = True
intCurrentRecord = intCurrentRecord + 1
If arStore(intCurrentRecord).Flag = True Then
cmdNext.Enabled = True
Else
cmdNext.Enabled = False
End If
RecordRead intCurrentRecord
If intCurrentRecord = intQuestions Then
fEnd = True
cmdNext.Caption = captFinish

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

В обработчик нажатия кнопки cmdPrev следует внести строку «разблокирования» кнопки cmdNext.

  • Перейдите в нужный обработчик.

Не забудьте, что код следует поместить уже после изменения счетчика intCurrentRecord.

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

intCurrentRecord = intCurrentRecord – 1
fEnd = False
cmdNext.Caption = captNext
cmdNext.Enabled = True

If intCurrentRecord = 1 Then

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

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

Private Sub optAnswer1_Click()
intAnswer = 1
arStore(intCurrentRecord).Flag = True
    cmdNext.Enabled = True
End Sub

  • Точно так же измените остальные два обработчика.

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

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

Отладка

Все работает, за одним исключением. А именно, сразу после запуска теста, пока вы находитесь на первом вопросе, кнопка cmdNext разблокирована!

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

Блокирование кнопки при запуске приложения

Проще всего сразу заблокировать кнопку cmdNext при запуске формы.

  • Вспомните, где назначались надписи кнопкам и поместите код:

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

cmdNext.Enabled = False
End Sub

  • Сохраните проект и запустите его.

Все работает!

Подподцикл 2. Восстановление ответов на переключателях

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

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

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

Проектирование

  • Составим схему последовательности действий:

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

  • Составим псевдокод:

<процедура>
‘для каждого переключателя
<считываем значение>
<помещаем его в переключатель>

<Считывание значения> из временного хранилища происходит так:

<значение> = arStore(index).Answ

Помещение значения в переключатель – более сложное действие.

Значение переключателя («включено-выключено») определяется свойством Value. Для переключателей (OptionButton) в VBA это свойство может принимать лишь два значения – True и False.

Но в хранилище находится номер полученного ответа. Следует преобразовать номер в значение True для переключателя.

К сожалению, VBA не позволяет организовывать массивы элементов управления (в отличие от своего «брата» – VB). Если бы массивы контролов были возможны, код выглядел бы так (проанализируйте его сами):

optAnswer(arStore(index).Answ).Value = True

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

Конструирование процедуры проверки полученного ответа и установки переключателя

Первое, что следует сделать – «сбросить» все переключатели, чтобы ни один не был включен.

Далее создадим проверку If...Then...Else, аргументом конструкции будет значение полученного ответа. В зависимости от результата проверки будет установлен тот или другой переключатель.

  • Оформим схему (в предположении, что переключателей  – и ответов, – всего 3):

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

Заметьте, что мы, помимо проверки номера полученного ответа, предусмотрели проверку на допустимость ответа вообще (внизу схемы). Так общепринято проверять данные. Действительно, где гарантия, что некий злоумышленник не испортил данные в таблице Excel? Если не сделать подобную проверку, тест-программа может «рухнуть» или, по меньшей мере, выдать неудобопонятные значения, а вы не будете знать, в чем дело.

  • Оформим псевдокод.

<Процедура>
‘сброс переключателей
optAnswer1.Value = False
optAnswer2.Value = False
optAnswer3.Value = False

‘проверка значения полученного ответа
If arStore(<индекс>).Answ = 1 Then
     optAnswer1.Value = True
ElseIf arStore(<индекс>).Answ = 2 Then
     optAnswer2.Value = True
ElseIf arStore(<индекс>).Answ = 3 Then
     optAnswer3.Value = True
Else
     <ошибка>
End If

Как видите, псевдокод очень похож на код VBA! Это значит, что схема, составленная нами, удовлетворительна.

Обратите внимание на <индекс>. Очевидно, что это – порядковый номер текущей записи – intCurrentRecord. Можно брать значение этой переменной, а можно создать для процедуры параметр. Методологически правильным будет создание параметра.

Решим, каким образом наша процедура будет сообщать программе об ошибке.

Функция, код ошибки

В таких языках, как C, ошибки обрабатываются общепринятым способом. А именно, процедура возвращает значение, говорящее об ошибке.

Вспомним понятие функции.

Функция – процедура, возвращающая значение.

Этого определения на настоящем этапе вполне достаточно.

Синтаксис функции сходен с таковым для процедуры:

Function <имя_функции>(<список_параметров>) As <тип>
.
.
.
<имя_функции> = <значение>
.
.
End Function

Первая строка почти такая же, как у процедуры. Разница в слове FunctionФункция, вместо Sub, и в обязательном указании <типа> возвращаемого значения. От версии к версии VBA список допустимых <типов> может дополняться. Подробнее об этом – в справке VBA.

Завершающая строка также не должна вызвать затруднений.

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

  • Теперь создадим псевдокод нашей функции.

<имя>() As Boolean
‘предположим, что возврат функцией значения True
‘говорит об успешном ее выполнении,
‘а возврат False – об ошибке

‘заранее предположим ошибку
<имя> = False
.
.
.
‘в случае успеха
<имя> = True
.
.
.
End Function

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

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

  • Объединим оба псевдокода. Не забудьте о создании параметра.

Function SetOptBtn(ind As Integer) As Boolean
‘предположим, что возврат функцией значения True
‘говорит об успешном ее выполнении,
‘а возврат False – об ошибке

‘заранее предположим ошибку
SetOptBtn = False

‘сброс переключателей
optAnswer1.Value = False
optAnswer2.Value = False
optAnswer3.Value = False

‘проверка значения полученного ответа
If arStore(ind).Answ = 1 Then
     optAnswer1.Value = True
ElseIf arStore(ind).Answ = 2 Then
     optAnswer2.Value = True
ElseIf arStore(ind).Answ = 3 Then
     optAnswer3.Value = True
Else
     Exit Function
End If
SetOptBtn = True
End Function

Обратите внимание на последний блок Else. Строка

Exit Function

принуждает VBA совершить выход из функции без выполнения дальнейшего кода. Здесь выход будет произведен в случае ошибки. А если выполнение продолжится – что может случиться только при «легальном» выполнении, – то функции вернет значение True.

Определим, в каком модуле следует расположить эту функцию.

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

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

То есть все слова типа

optAnswer1

следует заменить на

frmTest.optAnswer1

В новой функции шесть обращений к элементам управления, размещенным на форме. Есть смысл применить конструкцию With.

  • Поместите код функции в конструкцию With, а перед именами контролов поставьте точки.

Полностью функция будет выглядеть так:

Реализация

Public Function SetOptBtn(ind As Integer) As Boolean

SetOptBtn = False

With frmTest

.optAnswer1.Value = False
.optAnswer2.Value = False
.optAnswer3.Value = False


If arStore(ind).Answ = 1 Then
.optAnswer1.Value = True
ElseIf arStore(ind).Answ = 2 Then
.optAnswer2.Value = True
ElseIf arStore(ind).Answ = 3 Then
.optAnswer3.Value = True
ElseIf
Exit Function
End If
SetOptBtn = True
End With
End Function

  • В среде VBA откройте окно кода модуля basShell, перейдите в конец кода и внесите текст функции.

Теперь надо решить, в какой момент будет происходить вызов этой функции.

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

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

Кратко рассмотрим способ проверки ошибок, применяемый в данном случае.

Вызываемая функция возвращает код ошибки (или успеха, – мысля абстрактно, успех есть такая ошибка, при которой выполнение прошло нормально, «нулевая», «пустая» ошибка). В коде, в том месте, где происходит обращение к функции, помещается конструкция If...Then, где и проверяется значение, возвращенное функцией. В результате проверки программа «решает», какие действия будут производиться далее.

  • Псевдокод:

If <функция> = <код успеха> Then
     <действия при успешном вызове функции>
ElseIf <функция> = <код ошибки> Then
     <действия при ошибке выполнения функции>
ElseIf…
.
.
End If

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

  • Сделаем так.

Действия при ошибке

  • Вместо строки

RecordRead intCurrentRecord

  • поместим конструкцию:

RecordRead intCurrentRecord
If SetOptBtn(intCurrentRecord) = False Then
     MsgBox “Неверно значение полученного ответа!”
End If

Сочетания вызова функции и проверки условия

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

Вот как делает VBA незаметно для программиста:

<промежуточная переменная> = SetOptBtn(intCurrentRecord)
If <промежуточная переменная> = False Then
     MsgBox “Неверно значение полученного ответа!”
End If

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

Давайте отредактируем код.

Первый раз процедура RecordRead вызывается при запуске приложения, в процедуре StartTest.

  • В среде VBA откройте окно кода модуля basShell и перейдите в процедуру StartTest
  • Внесите изменения.
  • Теперь следует сделать подобное и в других местах проекта.

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

Среда VBA имеет средство для облегчения задачи.

  • Перейдите в окно кода модуля basShell, в текст процедуры StartTest.
  • Щелкните один раз на слове, которое следует найти в других местах проекта. В нашем случае это – имя процедуры RecordRead:

  • Теперь выполните команду меню IDE VBA EditFind или нажмите [Ctrl]+[F].

Появится диалоговое окошко поиска:

Как видите, в верхнем поле – слово, выбранное нами перед вызовом окна.

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

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

  • Сделайте нужное переключение:

  • И теперь щелкните кнопку  Find NextИскать Следующее.

Вы увидите, что в окне кода появился участок текста, содержащий искомое слово.

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

  • Совершите нужные изменения кода.

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

Как видите, произошло «размножение» строки-сообщения об ошибке. Следует исправить эту ситуацию.

  • Создайте в начале модуля basShell константу, которая и будет содержать строку, сообщающую об ошибке.

Public Const strError As String = "Неверно значение полученного ответа!"

После чего все строки

MsgBox "Неверно значение полученного ответа!"

  • замените на

MsgBox strError

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

  • Сохраните проект. Сделайте пробный запуск.
  • Попробуйте несколько раз выполнить переход по списку вопросов «вперед-назад», «отвечая» на вопросы. Следите, как будут изменяться значения переключателей при переходах.

Второй «виток» создания тестового приложения закончен.

  • Не забудьте внести комментарии в код!

Итог занятия.

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

Обратите внимание, что мы активно использовали конструкции If...Then и For...Next, которые теоретически изучались в первой части учебника. Тогда вам давался синтаксис и объяснение – сейчас же перед нами примеры использования. В случае затруднений – перелистайте начало учебника (хотя лучше было бы вновь составить конспект).

Кроме того, вы познакомились с приемами составления строк из констант и переменных (правильное название – конкатенация строк) и с некоторыми приемами работы в IDE VBA.

Важная теоретическая составляющая занятия – понятие функции.

Можете написать на эту тему несколько строк в свою тетрадь конспектов.

«Многоэтажная» конструкция If...Then может быть заменена другой конструкцией, имеющейся в языке VBA – Select Case (Выбрать в Случае). Изучите ее самостоятельно и занесите описание в конспект. Но заметим, что «многоэтажный» If...Then совершенно аналогичен Select Case и выбор – в ваших личных предпочтениях, поэтому мы и не применяли новую конструкцию в коде.

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

Hosted by uCoz