Глава 14:Событийно-управляемые приложения в FoxPro 2.5
В своем журнале я напечатал четыре статьи, посвященные созданию событийно-управляемых приложений. Каждое имеет преимущества и недостатки, но все используют одни и те же инструменты для достижения цели. Когда мне понадобился пример для использования в книге я выбрал материал Билла Довиса за чистоту изложения и качество кода. Билл - уважаемый консультант, работающий в Атланте. Для книги вам нужен код, который легко читается, именно этим качеством отличаются программы, написанные Биллом. Кроме того, Билл помог мне в написании самой главы, за что я ему крайне признателен. Вы можете связаться с Биллом по телефону 1-(404) 256-9869.
Модель событийно-управляемого приложения
Давайте сведем воедино то, что определяет модель событийно- управляемого приложения в FoxPro. Программирование событийно- управляемого приложения в основе своей подразумевает, что вы можете иметь на экране компьютера несколько экранов, и при переключении между ними с помощью мыши происходит активизация кода, управляющего работой каждого экрана. Если экран закрыт другими, вы можете использовать меню для вызова его наверх. Экран существует до тех пор пока пользователь не закроет его. Приложение ожидает возникновения некоторого "события" (выбора опции меню, щелчка мыши), это и дает имя термину "событийно- управляемое".
В отличие от Visual Basic или Clipper 5.0 FoxPro не входит в состояние ожидания события сам по себе. Вам необходимо создать это состояние. Это делается использованием команды READ.
READ ожидает ввода данных в поле, созданное командой @ ...GET, как это было всегда, но кроме того, команда готова к реакции на любое событие, которое потенциально стремится завершить ее. Именно для обработки такого события существует предложение VALID.
Если мы используем конструкцию READ VALID func(), то READ остается активной до тех пор пока func() не вернет .Т. (истина). До тех пор приложение может перебрасывать управление подобно волейбольному мячу. На этом, правда, простота заканчивается. Начиная с этого момента ситуация начинает усложняться.
Игры внутри игр
При выборе опции меню FoxPro выполняет связанную с ней команду или процедуру. Однако нет гарантии, что любое ваше действие вызовет срабатывание логики, связанной с предложением VALID, при команде READ. Оказывается, вам нужно позаботиться и об этом. При щелчке мышью на окне, расположенном не на самом верху, вам нужно отыскать и запустить соответствующий .SPR-модуль. FoxPro не предлагает механизма, позволяющего сделать это автоматически. Тем не менее, есть несколько точек, где вы можете вмешаться в естественный ход событий и направить их в удобное для вас русло.
Одна из таких точек обеспечивается предложением READ DEACTIVATE в .SPR-модуле. Генератор экранов FoxPro снабжает каждый экран или набор экранов командой READ, к которой вы можете прицепить блоки кода. Эти блоки связываются с предложениями, которые могут быть использованы совместно с READ: SHOW, SETUP, CLEANUP, ACTIVATE и DEACTIVATE. Эти предложения составляют первую часть ключа к программированию событийно-управляемых приложений в FoxPro, READ VALID - вторая его часть, но об этом мы поговорим позже.
Когда GENSCRN выполняет генерацию кода, установочный блок команд (Setup Code) размещается в самом начале создаваемой программы, а закрывающий блок (Cleanup Code) попадает в самый конец. В середине SPR-файла генератор помещает команды GET, за которыми следует READ. READ может иметь до трех предложений, каждому из которых может соответствовать блок кода или вызов процедуры.
Код, связанный с предложением SHOW, выполняется каждый раз при выполнении команды SHOW GETS, так что сюда уместно вставить логику, которая обновляет содержимое текущего экрана. Если у вас нечего обновлять кроме содержимого полей GET, оставьте это предложение пустым. Однако, для событийно- управляемых приложений код, связанный с предложениями ACTIVATE и DEACTIVATE, становится исключительно важным.
Код, связанный с ACTIVATE, выполняется каждый раз при попытке вывести соответствующий экран наверх, сделать его активным. В том случае если ваша программа имеет элементы управления для перемещения по записям, то вполне возможно, что вы вставите код, гарантирующий вам переход на нужную рабочую область для этого экрана, что-нибудь вроде SELECT <имя рабочей области>. Это наиболее часто встречающееся использование предложения ACTIVATE и часто единственное.
DEACTIVATE наоборот играет исключительно важную роль при создании событийно-управляемых приложений. Попробуйте такую штуку: запустите FoxPro, определите пару окон и активизируйте оба. Теперь поочередно щелкните мышью на каждом из окон и открыв меню Window (Alt+W) посмотрите, что делается с порядком перечисления имен ваших окон. Вы увидите, что порядок меняется. Именно в этом заключается подход к построению событийно - управляемых приложений.
Во многих событийно-управляемых приложениях вы найдете функцию, которая читает список имен окон для определения последнего элемента. Зачем? Неужели нельзя использовать функцию WONTOP()? Можно, если вы не используете наборы экранов (screen sets). Сейчас распространенной практикой стало использование единого окна с элементами управления для перехода между записями, переключением между режимами и т. п. Для получения такой функциональности вам необходимо создать набор экранов, состоящий из окна Control и вашего окна, после чего в код, связанный с предложением ACTIVATE, обязательно нужно добавить следующие строки:
ACTIVATE WINDOW (имя вашего окна)
ACTIVATE WINDOW CONTROL
В результате получается, что ваше окно не располагается сверху, оно предпоследнее. (На практике вы прочитываете список окон сверху вниз, сохраняя имена окон кроме имени Control).
Аналогично нет красивого способа определить, какое окно только что было закрыто. Поэтому общепринятым стал прием, когда имя закрываемого окна передается как параметр через код в READ DEACTIVATE.
Как же делается это событийно-управляемое программирование? Во-первых, мы предполагаем, что до тех пор пока на экране не появилось хотя бы одно активное окно, пользователь будет открывать окна, выбирая их из меню. Используем команду:
ON SELECTION BAR 1 OF database DO nextprog WITH 'Contact.Spr'
В прилагаемую программу входит функция NextProg, представленная в Листинге 14-1 и в конце Листинга 14-3.
Это означает, что "если в данный момент нет активной процедуры, запускаем ту, что была выбрана из меню, в противном случае меняем значение переменной NextProgram и выдаем CLEAR READ".
NextProgram называется "очередью событий". В сложных приложениях, таких как Tom Rettig's Office, очередь событий может быть многоуровневой, вам может быть необходимо выполнить несколько обязательных действий при завершении определенного режима и именно здесь требуется создать механизм определения действий в зависимости от вашего положения. Событийно - - управляемое программирование не для слабаков. В нашем простом примере, тем не менее, очередь тоже несложная.
Зачем CLEAR READ? Почему нельзя просто DO (NewProgram)? Причина в том, что вам нужно почистить за текущей задачей, и CLEAR READ принуждает к выполнению предложения READ VALID. Такой подход позволяет вам направить все процедуры модификации экрана через единый канал.
Процедура, выполняющаяся по READ VALID, называется Events. Она представлена в Листинге 14-2.
Произойти могут три взаимоисключающих события:
1) выполнение приложения закончено - пользователь выбрал Выход в меню;
2) пользователь выбрал другой режим работы в меню;
3) пользователь щелкнул мышью на окне.
В нашем примере используются имена окон, оканчивающиеся цифрой, я поступил так для упрощения кода в READ VALID. Если окно имеет имя multiwin, тогда MultiRead включает номер окна, который может быть использован для восстановления имени окна. Это упрощает чтение кода. В ваших программах может и не быть связанных в группы окон, но если они есть, то мой метод работает вполне прилично.
Вот как работает событийно-управляемое приложение в FoxPro. В Visual FoxPro вам может и не придется продираться через все эти сложности. Пока же это делается именно так.
Вариации на ту же тему
Я разработал несколько вариантов подхода к созданию событийно- управляемых приложений и думаю, что они окажутся интересными и полезными. Эти варианты описаны в следующих разделах.
Многостраничные формы
Пару месяцев назад меня попросили создать многостраничную форму. Я планировал использовать набор экранов - рекомендуемую технику программирования в FoxPro. Наборы экранов создаются в менеджере проектов путем добавления экранов к уже созданному. GenScrn затем создает код с определениями окон и GET для всех экранов как единую программу, и вам предоставляется возможность перемещения между окнами клавишами PgUp, PgDn. Сделать это довольно просто, но такой метод требует наличия активных полей ввода в каждом окне. В моем случае на экраны выводилась только справочная информация. Мне пришло в голову, что я могу сделать то, что от меня требуется с минимумом программирования.
CLEAR READ приводит к принудительному выполнению кода, связанного с READ VALID в foundation READ. Почему бы не использовать одно и то же имя окна в нескольких .SPR-файлах и просто вызывать разные программы без создания нового окна? Я попробовал и через несколько минут получил работающую модель.
Пример включает три файла Contact1, Contact2, Contact3. Окно имеет имя Contact во всех трех экранах. Помните, что GenScrn создает код такого вида:
IF WVISIBLE("contact")
ACTIVATE WINDOW contact SAME
ELSE
ACTIVATE WINDOW contact NOSHOW
ENDIF
... некоторый код...
IF NOT WVISIBLE("contact")
ACTIVATE WINDOW contact
ENDIF
Для вызова следующей "страницы" сделайте так:
NextProgram="Contact3.Spr"
CLEAR READ
В нашем примере GET должны занимать определенные позиции чтобы избежать наложений. Если бы не это я бы поступил так:
IF WONTOP()="CONTACT"
ACTIVATE WINDOW contact SAME
@10,1 CLEAR TO 15,57
ENDIF
Еще один подход вы можете найти если посмотрите код, организующий вывод нескольких экранов, когда пользователь выбирает опцию About из основного меню.
Отношения один-ко-многим
Один из методов поддержки родительских-дочерних файлов предполагает изменение размеров массива с данными из дочерних записей и вывод результатов в самом мощном из новых GET- объектов - прокручивающемся списке. Модификация массива производится в процедуре, связанной с предложением SHOW в родительском экране.
В нашем примере дочерние поля aid, date, time, type и contact размещены именно в таком массиве. Команда COPY TO ARRAY включает предложение ...FOR activity.id=contact.id для заполнения массива. После этого элементы массива форматируются таким образом:
FOR i = 1 TO nActivCnt
aActivity[i,6] = DTOC(aActivity[i,2]) + " | " + ;
aActivity[i,3] + " | " + ;
aActivity[i,4] + " | " + ;
aActivity[i,5]
ENDFOR
(То же самое можно получить одной командой на SQL, которая к тому же может быть и быстрее - прим. пер.):
SELECT ACTIVITY.AID, ACTIVITY.DATE, ACTIVITY.TIME, ACTIVITY.TYPE,;
ACTIVITY.CONTACT, DTOC(ACTIVITY.DATE) +' | ' + ACTIVITY.TIME +' | '+;
ACTIVITY.TYPE + ' | ' + ACTIVITY.CONTACT ;
FROM ACTIVITY, CONTACT ;
WHERE CONTACT.ID = ACTIVITY.ID ;
INTO ARRAY aActivity
Для прокручивающегося списка при создании экрана нужно пометить опцию 1st Element и ввести выражение (Expression) 6 (шестой элемент полученного массива должен быть выведен в список). После чего можно выбрав элемент из списка найти соответствующую запись в дочернем файле.
Код в предложении VALID выглядит так:
IF nActivCnt > 0
SELECT activity
LOCATE FOR aid=Aactivity[nActivity,1]
ENDIF
NextActivity = .T.
NextProgram = "activity.spr"
CLEAR READ
При этом мы можем найти дочернюю запись, если она существует, или добавить новую.
Шаблоны генератора
FoxPro 2.0 имеет несколько директив генератора - команд, управляющих генерацией кода. В версии 2.5 добавлено несколько других директив, некоторые из них весьма кстати.
Событийно-управляемые приложения FoxPro всегда имели огромное количество повторяющегося кода. Установочные и закрывающие части программ создания экранов были если не совершенно одинаковыми, то, во всяком случае, очень похожими. Версия 2.5 предлагает новую директиву, сокращающую опасность ошибки при многократном вводе одного и того же кода или при его копировании - это директива #INSERT.
Новая директива - чудесный инструмент. Если установочные, закрывающие, проверяющие и др. участки программ имеют общую функциональность вам достаточно написать код только один раз. В приводимом примере (Листинг 14-3) я предлагаю несколько стандартных процедур setup.gen (процедура установки), show.gen (процедура обновления переменных на экране), так же как и несколько специфических для приложения, но повторяющихся участков кода - contact.set (установочная часть кода) и contact.cln (завершающая часть кода). При необходимости изменить работу приложения вам нужно только внести коррективы в несколько стандартных модулей.
Учтите только, что изменение кода в стандартных процедурах не заставит менеджер проектов заново сгенерировать код для создания экранов. После внесения изменений вам нужно заново собрать проект с отмеченной опцией [ ] Build All. Можно также удалить .SPR файлы, в которые должны попасть измененные модули, но это легко забыть.
Я предложил вам несколько подходов. Уверен, что вы найдете множество других решений.