Продолжим изучать программирование на 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
Продолжение в следующем номере.