Продолжим изучать программирование на Visual Basic for Applications, уделив особое внимание некоторым «подводным камням», а также скрытым возможностям языка. Здесь речь пойдет о так называемых «пользовательских формах» (UserForms), формально являющихся «нестандартными диалоговыми окнами» программ и похожих на окна Word. Однако, по сути дела, они помогают создавать настоящие программы, практически подобные компилируемым для Windows, если не считать того, что для работы им требуется Microsoft Word. К сожалению, «пользовательские формы» нельзя образовать с помощью средства записи макросов. Все способы создания форм снабжены подробной справкой.

Чтобы организовать какую-либо пользовательскую форму, нужно в Менеджере проектов щелкнуть правой кнопкой мыши и выбрать в появившемся меню пункты «Вставить?UserForm».

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

Основное отличие текста программы, обслуживающей формы, от текста в обычных модулях заключается в принципе программирования. Программа на VBA (наподобие написанной в предыдущих статьях) выполняется «последовательно», т. е. все ее команды производятся одна за другой, и такой порядок изменяется только в зависимости от выбора операторов условного и безусловного перехода If и GoTo. А форма, в свою очередь, «реагирует на события», вследствие чего каждому ее элементу может быть поставлена в соответствие отдельная программа, производящая нужные действия. Большинство продуктов для Windows, да и сам текстовый редактор Microsoft Word построены именно по такому принципу — они не «работают сами по себе», а ждут от пользователя команд и «отвечают» на них, совершая какие-либо действия. Того же требует форма, для которой программист должен разработать интерфейс, — расположить на ней элементы и определить их свойства, а затем написать «программы обработки событий». Эти программы позволяют изменять внешний вид как самой формы, так и ее элементов, пряча или показывая их, а также модифицируя любые их свойства.

Окно дизайна форм

Для примера создадим форму, позволяющую подсчитать количество теплоты, выделяемой в проводнике при протекании по нему тока. Возьмем соответствующую формулу: Q=U2t/R, где Q — количество теплоты, Дж; U — напряжение, В; t — время, с; R — сопротивление, Ом. При этом R=pl/s, где p — удельное сопротивление материала проводника, Ом?м; l — длина проводника, м; s — площадь поперечного сечения проводника, см2. Таким образом, количества теплоты Q=(U2 x t x s)/(t x p).

Закон Джоуля—Ленца

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

Форма нашей программы. Вот что получилось

Поля ввода параметров имеют имена TextBox1...TextBox5 соответственно, поле отображения результата — TextBox6, кнопки сверху вниз — CommandButton1 и CommandButton2. В элементы TextBox1...TextBox5 пользователь будет вводить текст. А чтобы помешать пользователю случайно ввести текст в TextBox6, желательно установить его свойство Locked как True.

Установка свойства Locked как True

Разработка дизайна программы — ответственный момент, но не менее важно продумать, на каких принципах построить работу программы. Помните, что устранить ошибку на стадии проектирования программы в несколько раз легче, чем исправить ее на стадии реализации, в десятки раз легче, чем на стадии распространения, и в сотни раз легче, чем на стадии внедрения. Можно сделать, например, так: пользователь вводит все значения, нажимает кнопку «Подсчитать» (надо будет добавить на форму...) и получает в окне результата значение. Но в этом случае он вынужден, во-первых, выполнять лишнее действие — нажимать на кнопку, а во-вторых, надо будет продумать систему защиты от неправильных действий пользователя — нельзя допускать ввод нулевых или нечисловых значений в поля TextBox4 и TextBox5. Так что придется предусмотреть либо выдачу сообщения о неправильном вводе, либо, что представляется более верным, ставить в эти поля значения по умолчанию, если пользователь сделает неправильный ввод и уберет курсор. Но все же как это неудобно! Может быть, надо получше спроектировать программу?

Таким образом можно открыть окно программного текста формы

Основной принцип здесь: сделай проще, но без ущерба для функций. Итак, требуется получить результат. А для этого должны быть определены все значения в полях ввода, причем два нижних (TextBox4 и TextBox5) ненулевые. Но есть ли такая возможность у языка Visual Basic for Applications? Проверим! Откроем окно программного текста формы (в Менеджере проектов щелкнем правой кнопкой мыши на нашей форме и выберем «Программа») и из ниспадающего списка в левом верхнем углу выберем, например, TextBox1.

Ниспадающий список в левом верхнем углу — навигатор по программам элементов формы

В результате появился фрагмент текста

Private Sub TextBox1_Change()
End Sub

Название параметра Change переводится с английского как «изменение». Текст, написанный в этой части (называемой «обработчик события Change») программы, должен всякий раз выполняться, когда происходит это событие. Можно предположить, что оно совершается, если в поле ввода включили какой-либо символ или удалили его оттуда. Чтобы проверить, напишем:

Private Sub TextBox1_Change()
TextBox6.Text=TextBox1.Text
End Sub

Пусть для эксперимента при изменении текста в первом поле ввода произойдет изменение текста в поле отображения результата. Проверим, будет ли это работать. Нажмем клавишу (запуск программы) и поместим текст в первое поле ввода. Прекрасно, в поле отображения результата появляется тот же текст! Значит, в нашей программе надо использовать именно событие Change, чтобы после каждого нового ввода данных пользователем проверять условия возможности отображения результата и отображать его в случае их выполнения.

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

Переведем на язык VBA условие возможности отображения результата. Во-первых, все значения полей ввода должны быть числовыми. Для проверки этого в Visual Basic for Applications есть специальная функция IsNumeric (ее описание можно найти в справочной системе по словам «строковое выражение, числовое значение»).

Справка по функции IsNumeric

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

IsNumeric(TextBox1.Text) = True And
 IsNumeric(TextBox2.Text) = True And
 IsNumeric(TextBox3.Text) = True And
 IsNumeric(TextBox4.Text) = True And
 IsNumeric(TextBox5.Text) = True And Not 
 Val(TextBox4.Text) = 0 And Not 
 Val(TextBox5.Text) = 0.

В этом случае можно провести расчет по формуле

rez = ((Val(TextBox1.Text) ^ 2) * 
Val(TextBox2.Text) * Val(TextBox3.Text)) / 
(Val(TextBox4.Text) * Val(TextBox5.Text))

и отобразить значение в поле вывода результата:

TextBox6.Text = Str$(rez).

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

Private Sub TextBox1_Change()
Scet
...
Private Sub TextBox5_Change()
Scet
End Sub
Private Sub Scet()
If IsNumeric(TextBox1.Text) = True And 
IsNumeric(TextBox2.Text) = True And 
IsNumeric(TextBox3.Text) = True And 
IsNumeric(TextBox4.Text) = True And 
IsNumeric(TextBox5.Text) = True And Not 
Val(TextBox4.Text) = 0 And Not 
Val(TextBox5.Text) = 0 Then
rez = ((Val(TextBox1.Text) ^ 2) * 
Val(TextBox2.Text) * Val(TextBox3.Text)) / 
(Val(TextBox4.Text) * Val(TextBox5.Text))
TextBox6.Text = Str$(rez)
Else
TextBox6.Text = «»
End If
End Sub

В принципе программа уже почти закончена, но предстоит еще разобраться с командными кнопками. Для кнопки «Отмена» обработчик события Click (нажатие на кнопку) прост — выход из программы:

Private Sub CommandButton2_Click()
Unload Me
End Sub

Но у нас используется еще одна кнопка — «Вставить результат в документ». Сделаем так, чтобы при ее нажатии в документ вставлялось не только значение результата, но и введенные параметры. Это можно сделать с помощью команды:

Selection.Text = ?При прохождении тока
 напряжением в ? + TextBox1.Text + ? вольт по
 проводнику длиной ? + TextBox4.Text + ? метров,
 сечением ? + TextBox3.Text + ? кв.мм и удельным
 сопротивлением ? + TextBox5.Text + ? ом на метр
 за ? + TextBox2.Text + ? секунд выделится? +
 TextBox6.Text + ? джоулей теплоты. ?
Список методов объекта Selection

Она сформирует фразу из значений полей ввода и вставит ее в активный документ. Команда выполняется, но фраза останется выделенной. Значит, следующая выведенная с помощью нашей программы фраза сотрет предыдущую. Посмотрим, есть ли в VBA функция снятия выделения? Находим команду Collapse (т. е. «Свернуть»).

Из справочной системы узнаем ее синтаксис:

Selection.Collapse Direction:=wdCollapseEnd.

Эта команда снимает выделение и помещает курсор в конец фразы.

Справка по команде Collapse

Можно также в активный документ поместить текст: Selection. TypeText Text:=«Мой текст» (двоеточие после слова Text ставится обязательно). Тогда не нужно специально снимать выделение — это будет делаться автоматически.

Если пользователь вызовет программу, когда в Word нет открытых документов, то возникнет ошибка. Этого легко избежать: надо просто проверить, перед тем как вставлять фразу, есть ли открытые документы. Если их не окажется, то можно создать новый:

If Documents.Count = 0 Then Documents.Add

Осталась еще одна маленькая деталь. Кнопка «Вставить результат в документ» не должна работать, если результат вычислить нельзя (т. е. поле TextBox6 пусто). Как это обеспечить? В набор возможных свойств элемента CommandButton входит Enabled. И если его установить как False (т. е. «ложно»), то кнопка отобразится серым цветом и не будет реагировать на события (станет неактивной).

Свойство элемента CommandButton

Свойство Enabled можно задать и программно, командой CommandButton1.Enabled = False. Поставим в процедуру вычисления результата пару команд, активизирующих кнопку, когда определяется результат и когда его можно вставить в текст, и инактивирующих ее в противном случае. Также вначале зададим для свойства Enabled этой кнопки значение False, чтобы она оставалась неактивной до тех пор, пока не произойдет ввод в какое-либо окно символов и не начнется срабатывание процедуры вычисления результата.

Готовый текст программы:

Private Sub CommandButton1_Click()
If Documents.Count = 0 Then Documents.Add
Selection.Text = ?При прохождении тока
 напряжением в ? + TextBox1.Text + ? вольт
 по проводнику длиной ? + TextBox4.Text +
 ? метров, сечением ? + TextBox3.Text + ? кв.мм
 и удельным сопротивлением ? + TextBox5.Text
 + ? ом на метр за ? + TextBox2.Text + ? секунд
 выделится? + TextBox6.Text + ? джоулей теплоты. ?
Selection.Collapse Direction:=wdCollapseEnd
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Private Sub TextBox1_Change()
Scet
End Sub
Private Sub TextBox2_Change()
Scet
End Sub
Private Sub TextBox3_Change()
Scet
End Sub
Private Sub TextBox4_Change()
Scet
End Sub
Private Sub TextBox5_Change()
Scet
End Sub
Private Sub Scet()
If IsNumeric(TextBox1.Text) = True And
 IsNumeric(TextBox2.Text) = True And
 IsNumeric(TextBox3.Text) = True And
 IsNumeric(TextBox4.Text) = True And
 IsNumeric(TextBox5.Text) = True And Not
 Val(TextBox4.Text) = 0 And Not
 Val(TextBox5.Text) = 0 Then
rez = ((Val(TextBox1.Text) ^ 2) *
 Val(TextBox2.Text) * Val(TextBox3.Text)) /
 (Val(TextBox4.Text) * Val(TextBox5.Text))
TextBox6.Text = Str$(rez)
CommandButton1.Enabled = True
Else
TextBox6.Text = «»
CommandButton1.Enabled = False
End If
End Sub

Назначить форме кнопку или пункт меню для вызова из Word нельзя — это можно делать только для модулей. Поэтому, например, переименуем для красоты форму в Teplotok (свойство Name объекта UserForm можно задать в окне свойств, выделив форму) и напишем модуль, в котором будет всего одна команда — вызов созданной нами формы.

Sub TeploCount()
Teplotok.Show
End Sub
Процесс присвоения модулю имени

Присвоим модулю красивое имя, например Teplo. (Если модуль для программы вызова формы был вставлен с использованием пункта «Вставить?Модуль» из контекстного меню, полученного при щелчке правой кнопки мыши в Менеджере проектов, то дать название можно с помощью свойства Name объекта «Модуль1».) Затем назначим в Word кнопку для вызова макроса Normal. Teplo.TeploCount. Вот и все — наша программа готова.

Готовая программа

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

Антон Александрович Орлов, antorlov@inbox.ru, http://antorlov.chat.ru

Продолжение в следующем номере.