Стивен
Ферстайн.
Навигация
и передача параметров между формами в
Oracle Forms. Построение
наборов процедур
многократного
использования.
(Inter-Form Navigation and
Communication in Oracle Forms Building a Reusable
Set of Procedures by Steven
Feuerstein, SELECT september
94).
Тема №1. Управление транзакциями в формах.
Тема №2. Пересылка Информации.
Тема №3. Закрытие Экранной Формы (Screen Closure).
Тема №4. Единое Решение
Всех Проблем.
Тема №5.
Проектирование Элементов Формы.
Тема №7. Передача Данных к Вызываемой
Форме и от нее.
Тема №8 .Инициализация Вызываемой Формы.
Тема №9. Сохранение Изменений в Вызываемой
Форме.
Тема №10. Закрытие и Выход из Вызываемой
Формы.
Тема №11. Несколько слов о NEW_FORM.
Тема №12. Преимущества Стандартного
Процедурного Интерфейса.
Почти в любом приложении пользователям хочется
иметь возможность
легко переходить от одной формы к другой. При этом навигационная
модель может быть иерархической, в которой
пользователь может
последовательно входить в различные уровни детализации
(базирующиеся обычно
на отношениях внешнего ключа), и выходить из
них.
Возьмем пример системы регистрации телефонных
сообщений.
Допустим, поступило телефонное сообщение от
человека, являющегося
сотрудником некоторой компании. Когда сообщение принято,
оператор может получить более подробную
информацию о звонившем,
раскрыв соответствующий экран. Таким образом, входя
дальше в
экран компании, оператор получает полную
информацию о компании,
сотрудник которой сделал этот вызов.
(В
дальнейшем в связи с отсутствием в русском языке короткого
перевода английского слова Caller,
дословно означающего в
этом контексте "Тот, кто звонит по телефону",
я буду заменять его
словом "Клиент" - прим. переводчика).
Затем пользователь выходит из каждого
вложенного экрана, и,
наконец, возвращается в экран регистрации
телефонных сообщений
или, возможно, в меню.
Другой общей моделью является сеть, в которой пользователь
может
подключаться к некоторому количеству связанных (или не связанных)
друг с другом форм из любой формы. Несомненно,
меню служит как бы
стартовой площадкой для такого рода сетей. Даже
экран входных
данных может предоставлять пользователю возможность
перемещаться
непосредственно к другому разделу приложения
простым нажатием
кнопки. Каждый экран в приведенном выше примере
системы
регистрации телефонных сообщений может иметь
дополнительные
кнопки, с помощью которых пользователь может
подключаться
непосредственно к подсистеме подготовки
отчетов, или к экрану
заметок, чтобы записать подробности вызова, или
к системе
регистрации времени, чтобы записать время и
день недели. Таким
образом происходит закрытие экранов и изменение
положения
пользователя в приложении.
Oracle Forms обеспечивает
навигацию между формами с помощью двух
встроенных процедур: CALL_FORM и NEW_FORM.
CALL_FORM вызывает
другую форму, сохраняя в памяти вызвавшую
форму. При выходе из
вызванной формы пользователь вновь возвращается
в форму, которая
ее вызывала. NEW_FORM, напротив, закрывает
старую форму и
открывает новую. Выход из вызванной формы не
возвращает
пользователя в вызвавшую ее форму; вместо этого
происходит выход
из сессии Runform.
CALL_FORM сохраняет положение в вызывающей
форме (и текущую транзакцию), но также требует
больше памяти
(чтобы сохранять все активные формы доступными). При
использовании NEW_FORM теряется контекст, зато освобождается
вся
память, занятая вызывающей формой.
Подобно многим другим средствам, которые предоставляет
Oracle
Forms, CALL_FORM и NEW_FORM очень просты
в использовании, однако
при этом требуют особого мастерства. Можно
мгновенно освоить
синтаксические правила вызова формы B из формы
A. Но, погружаясь
в мир реальных требований вашего приложения, Вы
обнаружите, что
навигация между формами может быть очень
сложной. И даже если Вы
или кто-нибудь из персонала разработчиков
специально следит за
соблюдением всех правил и методов навигации,
вряд ли Вы сможете
дать гарантию того, что Ваше приложение выполнено
в
последовательной, высококачественной
манере? Эта статья
посвящена некоторым проблемам передачи параметров и
навигации
между формами. Она предлагает проект
процедурного интерфейса,
который обеспечит всех Ваших разработчиков единым,
хорошо
проверенным решением.
Тема
№1. Управление транзакциями в формах.
Если у есть
незафиксированные изменения в форме A, и в это время
вызвана форма B, то выполнить фиксацию (commit) в форме B
невозможно. Можно только переслать (post) в базу данных изменения
в вызванной форме. При этом изменения
пересылаются в базу данных,
но не фиксируются. Они будут видимы в текущей
сессии, но невидимы
для других пользователей. До тех пор, пока транзакция не
завершена,
замки в вызвавшей форме (и всех предшествовавших
вызвавших формах)
действуют; транзакции, следовательно, могут
выполняться
с интервалами, зависящими от форм [Для получения
более подробной информации о процессе пересылки
(post) и фиксации
(commit) смотрите
Главу 20, "Многоформенные Приложения"
("Multiple-form Applications") в Руководстве Пользователя по
Oracle Forms Корпорации Oracle].
Хорошо, скажете Вы, но почему нельзя просто
переслать или
зафиксировать изменения в вызвавшей форме перед
переходом к форме
B? Изменения будут записаны в базу данных и
нормально
зафиксированы, после чего можно выделять место для вызванной
формы. Иногда можно делать и так; однако часто
пользователи
вызывают другую форму как раз для получения
информации, которая
необходима, чтобы убедиться, что в вызывающей форме можно
выполнить транзакцию.
Если Вы никогда явно не используете в своих
программах оператор
фиксации изменений (commit
statement), а оставляете его на
совести Oracle Forms, то программа сама будет определять, когда
она может выполнить пересылку (post), а когда - фиксацию
изменений (commit).
Однако, как Вы можете обнаружить, такая
практика редко бывает оправданной, особенно для графического
интерфейса пользователя. Например, как только
Вы примете решение
обеспечить сохранение изменений и закрытие окна
с помощью кнопки
(button), у Вас
появится возможность выполнять собственную
фиксацию (commit).
Более того, многие приложения требуют
комплексной проверки перед фиксацией изменений,
иногда вызывая
для этого форму, которая заканчивается явным
оператором фиксации
изменений (commit).
Если попытаться выполнить фиксацию изменений,
не выполнив в
вызывающей форме операцию POST, то Вы увидите
сообщение
"FRM-40403: В вызвавшей форме есть не
пересланные изменения.
Фиксация недоступна." Не
слишком дружественное сообщение. Если же
Вы перешлете изменения в базу данных перед тем,
как выполнять
фиксацию, то Ваши формы не будут следовать
стандартному процессу
обработки транзакций в формах.
Напомним, что в нижней части экрана всегда есть
строка сообщений,
которая относится к текущей (т.е. вызванной)
форме, и Вы можете
использовать ее для вывода необходимой Вам
информации о том, какой
тип сохранения будет использоваться: post или commit, однако в то же
время эти сложности необходимо спрятать от
конечного пользователя.
Тема
№2. Пересылка Информации.
Когда Вы переходите от одной формы к другой, Вы
одновременно
передаете информацию. Этот процесс тоже может
вызывать некоторые
затруднения разработчиков.
Один из лучших общепринятых способов передачи
данных между
формами использует внешние ключи. Вернемся
снова к системе
регистрации телефонных сообщений. Некто
обращается по телефону в
местное отделение с жалобой. Чтобы внести эту
жалобу в базу
данных, необходимо зарегистрировать клиента в
базе данных,
сгенерировав для него уникальный
идентификационный номер, и
сослаться на этот номер в записи о жалобе. Но
для того, чтобы
записать в базу данных информацию об этом
клиенте, необходимо
записать и другую относящуюся к делу
информацию, такую, например,
как компания, в которой он работает. Итак, от
экрана "Жалоба",
информация с которого пока не может быть зафиксирована,
необходимо обратиться к экрану
"Клиент". Сначала нужно ввести имя
клиента и другую информацию о нем, затем
вызвать экран "Компании"
и создать запись с информацией о компании.
Затем информация о
компании должна быть передана (post) в базу данных, должен быть
сгенерирован ее идентификатор, который
передается его назад в
экран "Клиент". После этого
генерируется и передается в базу
данных идентификатор клиента, затем его
идентификатор передается
дальше в экран "Жалоба" и, наконец,
жалоба фиксируется в базе
данных. Этот пример иллюстрирует передачу
параметров от
вызываемой форме назад к
вызывающей. Предположим, что теперь я
запрашиваю информацию о жалобе в
соответствующем экране и хочу
увидеть подробную информацию о клиенте. В этом
случае я перехожу
к экрану "Клиент", передавая
одновременно его идентификационный
номер из экрана "Жалоба", и
запрашиваю непосредственно информацию
о клиенте.
Заметьте, что один и тот же экран
"Клиент" вызывается и
используется по меньшей мере тремя различными способами, и
что
эти различные "режимы" экрана требуют
выполнения различных
действий для их запуска:
1. Для регистрации нового клиента при вводе
информации о жалобе.
Идентификатор клиента, обратившегося с жалобой, передается
назад
в вызывающую форму - если пользователь подтверждает
изменения, иначе происходит возврат в вызывающую форму без
пересылки идентификатора.
2. Для запроса существующей информации о
клиенте из экрана
"Жалоба". Идентификатор клиента принимается от вызывающей
формы.
Тогда может быть даже запрошена информация о разных
клиентах, и новый идентификатор клиента может быть передан
назад
в экран "Жалоба" для замены предыдущего идентификатора
клиента.
3. Для поддержки информации о клиентах как
автономная форма,
вызываемая из меню. В этом случае нет необходимости в
пересылке какой-либо информации в экран "Клиент" или из него.
Построение Ваших экранных форм займет больше
времени, если не
разработать согласованный способ поддержки всех
этих различных
режимов запуска. Вдобавок будет очень тяжело
отлаживать формы, а
пользователи, возможно, будут испытывать
неудобство в связи с
тем, что эти формы по-разному ведут себя в
приложении.
Тема
№3. Закрытие Экранной Формы (Screen Closure).
Как существуют три разных способа запуска и
использования формы,
так и для того, чтобы покинуть форму,
пользователь может
выполнить одно трех
действий:
1. OK и возврат в вызывающую форму/меню:
пользователь
подтверждает действие или текущее содержимое экрана. "OK"
может
иметь смысл "commit/post"
или использоваться в смысле
"передать текущую запись в вызывающую форму". Пользователь
может
выбрать эту опцию с помощью графической кнопки, из меню,
или
использовать для этого функциональный ключ. Вы можете
поддерживать все эти варианты.
2. Отмена (Cancel) и
возврат в вызывающую форму/меню:
пользователь отвергает сделанные изменения или выбранную
запись. Выход из формы происходит без пересылки назад
какой бы
то ни
было информации. Как и в предыдущем случае, пользователь
может
выбрать эту опцию с помощью графической кнопки, из меню,
или
использовать для этого функциональный ключ. Вы также
можете
поддерживать все эти варианты.
3. Выход из формы и всех вызывающих форм;
покинуть приложение:
пользователь не только не желает сохранять изменения или
пересылать информацию; пользователь желает покинуть все
приложение без явного выхода из каждой вызывающей формы. Это
очень
полезное средство (я называю его "жесткий выход") как во
время
отладки/тестирования, так и в готовой программе.
Пользователи могут оказаться очень глубоко во вложенных
друг в
друга формах и вдруг обнаружить, что рабочий день закончился и
пора
идти домой. Они возненавидят Ваше приложение за то, что
опоздали на свой трамвай, потому что должны были дюжину раз
нажать
клавишу "Exit" перед тем, как закончить
сеанс работы.
Итак, теперь Вы мне верите? Представляет
навигация между формами
некоторые потенциально сложные сценарии? И
хотите ли Вы позволить
каждому из своих разработчиков использовать их
собственные очень
разнообразные решения для этих сценариев? Или
Вы все-таки
построите хорошо спроектированные и тщательно
проверенные наборы
процедур, которые обеспечат связный интерфейс с
CALL_FORM и
NEW_FORM, и будут поддерживать решение всех
вышеперечисленных
сложностей?
Да что тут распинаться! И так все ясно ...
Тема
№4. Единое Решение Всех Проблем.
Каждая из вышеупомянутых проблем имеет решение.
Действительно,
один и тот же набор процедур обеспечивает
единое полное решение
всех вопросов. Возможно, Вы уже заметили, что
процесс фиксации
изменений во время навигации между формами
часто связан со
способом, которым пользователи выходят из
вызванной формы.
Если у пользователя уже есть несохраненные изменения,
то его
выход логически должен поддерживать пересылку
изменений (post)
вместо фиксации (commit).
Пересылка данных между формами
происходит так же, точно в момент инициализации
экрана и сразу
перед его закрытием. Мы исследуем эти решения
ниже,
заострив внимание на следующих общих аспектах:
1. Как форма вызывается.
2. Как вызванная форма инициализируется.
3. Как сохраняются изменения в вызванной форме.
4. Как происходит закрытие и выход из формы.
5. Как вызывающая форма реагирует, когда
управление возвращается
к ней
из вызванной формы.
Для каждого из этих шагов необходимо обеспечить
процедурный
интерфейс для решения всех Ваших проблем,
который любой
разработчик мог бы использовать, не заботясь об
этих сложностях в
своих программах. Этот набор процедур и/или функций становится
как бы промежуточным уровнем между
пользователем/разработчиком и
Oracle Forms. Он обеспечивает дополнительный уровень
управления
в Ваших приложениях. Если позднее Вы захотите расширить
функциональные возможности любого из пяти
аспектов, перечисленных
выше, внесите изменения в подходящую процедуру,
и расширенные
возможности станут
доступны всем формам, которые обращаются к
этой процедуре.
Учитывая ограниченный объем этой статьи, я
продемонстрирую
необходимый процедурный интерфейс только для
процедуры CALL_FORM.
В конце статьи я дам некоторые указания по
применению сходных
решений для процедуры NEW_FORM.
Тема
№5. Проектирование
Элементов Формы.
Каждая форма, использующая описанные ниже
компоненты, нуждается в
нескольких стандартных элементах, определяемых
в Oracle Forms
Designer.
а.
Параметры Формы:
Как показано ниже в разделе, посвященном
пересылке данных, Oracle
Forms обеспечивает определенные Вами значения
параметров по
умолчанию. Эти значения могут быть изменены
вызывающей формой.
PARAMETER.PR_commit_mode
Этот параметр имеет значение либо
"COMMIT", либо "POST" и
определяет, может ли форма делать полную
фиксацию изменений в
форме (COMMIT_FORM), или она может работать
только в режиме
пересылки данных (post).
Значением по умолчанию является
"COMMIT".
PARAMETER.PR_startup_mode
Этот параметр определяет режим запуска формы.
Будет ли форма
при запуске выполнять запрос, возвращающий все
записи, или только
одну запись? Позволяется ли пользователю в
режиме запроса вводить
критерий поиска? Или форма будет сразу же
устанавливаться в режим
ввода новой записи, в которую пользователь
может сразу начинать
вводить данные? Список возможных значений
включает: "EXEQRY",
"ENTQRY", "CREREC" и так
далее.
PARAMETER.PR_calling_form
Этот параметр содержит имя формы, вызвавшей
текущую форму.
Необходим в процедуре, выполняемой в триггере
When-New-Form-Instance, для возврата значения процедурой
GET_APPLICATION_PROPERTY
(CALLING_FORM).
PARAMETER.PR_called_form
Этот параметр содержит список имен всех форм,
которые вызываются
из текущей формы. Вы можете использовать этот
список, чтобы
показать пользователю путь доступа к формам,
или передать
управление особой форме в этой
последовательности форм.
PARAMETER.PR_mdi_window_title
Этот параметр содержит имя окна MDI (multiple document
interface -
интерфейса множества документов). Необходимость в
этом параметре является спецификой Windows. Устанавливается
это имя в заголовке окна MDI для сессии Runform. Поддержка
этого значения важна,
потому что Oracle Forms
автоматически не
восстанавливает имя окна при возврате из
вызванной формы. При
вызове любой формы это имя отображается в заголовке
окна. Наша
процедура будет автоматически восстанавливать
заголовок окна MDI
после закрытия формы.
PARAMETER.PR_doc_window_title
Этот параметр содержит имя текущего окна
документов в форме. Оно
отображается в полосе заголовка в пределах окна
MDI. Как и
предыдущий, этот параметр используется для
хранения значения во
время выполнения формы, а наша процедура может
использовать его
для
восстановления заголовка окна в при возвращении в форму.
b.ГЛОБАЛЬНЫЕ переменные.
GLOBAL.lib_exit_application
Эта глобальная переменная управляет всеми
формами приложения при
его закрытии
по команде exit из текущей формы (т.е. "жесткий
выход"). Если переменная GLOBAL.lib_exit_application
имеет
значение "Y" или "YES", то
при возвращении управления в
вызывающую форму библиотечная процедура
активизирует
непосредственно процедуру EXIT_FORM. Действие
переменной
распространяется на все приложения,
использующие этот
библиотечный код.
GLOBAL.app_menu_name
Эта глобальная переменная содержит имя формы,
которая
используется в качестве интерфейсного меню или
стартового меню
приложения. Эта глобальная переменная может
быть использована в
в нескольких разных целях. Ниже приведен пример
кода, который
инициализирует установку метки формы,
соответствующей кнопке
выхода, была
ли она вызвана пользователем из экрана меню (в этом
случае метка называется "Выход"), или
в экране, вызванном из
этого меню (тогда метка называется
"Возврат в меню"). Эта
глобальная переменная является переменной на
уровне приложения.
Имея в своем распоряжении мой процедурный
интерфейс, разработчики
(мои "пользователи") никогда не
используют оператор CALL_FORM в
своих программах.
Вместо этого они вызывают процедуру, которая
выполняет для них CALL_FORM, но кроме этого
делает и много
больше, чем просто CALL_FORM.
Вместо оператора:
CALL_FORM('myform',HIDE,DO_REPLACE,NO_QUERY_ONLY,param_list_id);
разработчик будет кодировать это действие
следующим образом:
lib_navigate.callform('myform',HIDE,
DO_REPLACE,NO_QUERY_ONLY,param_list_id);
где lib_navigate -
библиотечная пакетная PL/SQL-процедура,
которая поддерживает все формы в навигации между
формами.
Заметьте, что
процедуре lib_navigate передаются те же самые
определенные в Forms
константы, что и при вызове процедуры
CALL_FORM. Эти константы имеют тип NUMBER;
имена их
зарезервированы. Вы не знаете числового значения константы,
которое передаете своей собственной процедуре. Оставим
их
правильную трансляцию на совести Oracle Forms.
Здесь приводится мой первый "проход"
по библиотеке lib_navigate.
На нем я построю объяснение шагов построения
процедурного
интерфейса.
PACKAGE lib_navigate IS
/*
ЁЁ Этот пакет
состоит из процедуры callform и нескольких
ЁЁ внутренних процедур, которые вызываются
процедурой callform.
ЁЁ Процедура reset_form
сделана доступной для всех пользователей,
ЁЁ так что разработчики могут использовать ее
для переустановки
ЁЁ заголовков окон всегда, когда захотят. Вы
также можете
ЁЁ добавить другие средства
"регенерации" ("refresh") в эту
ЁЁ процедуру.
*/
PROCEDURE
reset_form;
PROCEDURE
callform (fname_in IN VARCHAR2,
hide_in IN
NUMBER := HIDE,
replace_in
IN NUMBER := DO_REPLACE,
query_in IN
NUMBER := NO_QUERY_ONLY,
plist_id_in IN PARAMLIST);
END lib_navigate;
PACKAGE
BODY lib_navigate IS
PROCEDURE
reset_form
IS
/*
ЁЁ Oracle Forms не восстанавливает автоматически имя окна при
ЁЁ возврате из
вызванной формы. Остаются заголовки форм, которые
ЁЁ перед этим вызывались. Эта процедура
возвращает заголовок
ЁЁ окна, который
сохраняется в параметре, после закрытия формы.
*/
BEGIN
SET_WINDOW_PROPERTY (FORMS_MDI_WINDOW_TITLE,
TITLE,
NAME_IN('PARAMETER.MDI_WINDOW_TITLE'));
SET_WINDOW_PROPERTY (ROOT_WINDOW, TITLE,
NAME_IN('PARAMETER.MDI_WINDOW_TITLE'));
/*
ЁЁ Добавлю: я обнаружил, что если в вызывающей форме есть
ЁЁ
кнопки, метки которых изменены программным способом, то при
ЁЁ возвращении из вызываемой формы этим меткам будут снова
ЁЁ
присвоены их значения по умолчанию. Неплохо было бы
ЁЁ
использовать общий метод их восстановления.
*/
IF NAME_IN('PARAMETER.pr_calling_form') =
NAME_IN('GLOBAL.app_menu_name')
THEN
--
Эта форма была вызвана из меню
SET_ITEM_PROPERTY('buttons.ok',LABEL,'Go To
Menu');
ELSE
--
Эта форма была вызвана из другой формы
SET_ITEM_PROPERTY('buttons.ok',LABEL,'Return');
END IF;
END;
PROCEDURE
post_callform(level_in IN VARCHAR2)
IS
/*
ЁЁ Эта процедура
вызывается после выполнения CALL_FORM;
ЁЁ Копируется
MESSAGE_LEVEL (уровень сообщений), сохраняемый в
ЁЁ переменной PL/SQL, назад в системную
переменную
ЁЁ SYSTEM.MESSAGE_LEVEL. Но даже более важным
является то, что
ЁЁ здесь обеспечивается опция "жесткий
выход". Жесткий выход
ЁЁ происходит тогда, когда пользователь хочет
немедленно выйти из
ЁЁ приложения, не обращая внимания на
вложенность формы. Вы
ЁЁ должны предложить опцию "жесткий
выход" в меню. Когда эта
ЁЁ опция выбрана, глобальная переменная
ЁЁ GLOBAL.lib_exit_application
устанавливается в значение "YES",
ЁЁ и затем выполняется EXIT_FORM. Если форма
была вызвана
ЁЁ посредством процедуры lib_navigate.callform,
то в процедуре
ЁЁ post_callform, которая выполняется следом и текст которой
ЁЁ помещен ниже, обнаруживается "жесткий
выход" и немедленно
ЁЁ выполняется ДРУГОЙ EXIT_FORM, и так, пока
не будет осуществлен
ЁЁ выход из всех форм.
*/
BEGIN
DEFAULT_VALUE('NO','GLOBAL.lib-exit-application');
-- На случай, если разработчик не может вспомнить, что
-- применять,
"YES" или
"Y"...
IF NAME_IN('GLOBAL.lib_exit_application') IN ('YES','Y')
THEN
/*
ЁЁ Если текущая форма находится в Режиме
Запроса (Query
ЁЁ Mode), то нужно выйти из этого режима
перед тем, как
ЁЁ покинуть форму. Установите уровень сообщений равным 10,
ЁЁ чтобы пользователь не видел сообщения "запрос отменен",
ЁЁ затем отмените запрос и восстановите значение уровня
ЁЁ сообщений.
*/
IF NAME_IN('SYSTEM.MODE')
= 'ENTER_QUERY'
THEN
COPY('10','SYSTEM.MESSAGE_LEVEL');
EXIT_FORM;
COPY(level_in,'SYSTEM.MESSAGE_LEVEL');
END
IF;
EXIT_FORM(NO_VALIDATE,FULL_ROLLBACK);
ELSE
/*
ЁЁ Сделайте так, как будто предыдущая форма
никогда не
ЁЁ вызывалась !
ЁЁ 1. Восстановите окна и метки кнопок.
ЁЁ 2. Восстановите уровень сообщений.
ЁЁ 3. Удалите текущую форму из списка форм, которые
ЁЁ вызывались. Для этого нужно
найти ПОСЛЕДНИЙ символ
ЁЁ "|" в строке,
используя для этого функцию INSTR с
ЁЁ обратным поиском (с конца
строки, а не с начала).
*/
lib_navigate.reset_form;
COPY(level_in,'SYSTEM.MESSAGE_LEVEL');
COPY(SUBSTR(NAME_IN('PARAMETER.pr_called_forms'),1,
INSTR(NAME_IN('PARAMETER.pr_called_forms'),
'|',-1,1)-1),
'PARAMETER.pr_called_forms');
END IF;
END;
PROCEDURE
callform (fname_in IN VARCHAR2,
hide_in IN
NUMBER := HIDE,
replace_in
IN NUMBER := DO_REPLACE,
query_in IN
NUMBER := NO_QUERY_ONLY,
plist_id IN PARAMLIST)
IS
/*
ЁЁ Эта процедура
заменяет CALL_FORM. При обращении к ней
ЁЁ используются те же самые параметры и в том
же порядке, с теми
ЁЁ же
значениями по умолчанию. Полное описание этих параметров
ЁЁ
смотрите в Руководстве Пользователя по Oracle Forms (Oracle
ЁЁ Forms
User's Guide).
*/
msglvl VARCHAR2(3);
source_loc VARCHAR2(100);
BEGIN
/*
ЁЁ Текущее значение уровня сообщений (MESSAGE_LEVEL)
ЁЁ сохраняется в переменной PL/SQL. Это значение нужно
ЁЁ
для восстановления уровня сообщений, какая бы форма ни
ЁЁ выполнялась перед этим.
*/
msglvl
:= NAME_IN('SYSTEM.MESSAGE_LEVEL');
/*
ЁЁ
Определим, не нужно ли вызываемую форму выполнять в
ЁЁ режиме "только передачи" (post-only).
Если вызывающая форма
ЁЁ уже была в этом режиме (на что указывает значение
ЁЁ PARAMETER.pr_commit_mode) или если в
текущей форме есть
ЁЁ несохраненные изменения (согласно значению системной
ЁЁ переменной SYSTEM.FORM_STATUS), то фиксация (COMMIT_FORM)
ЁЁ не будет выполняться в вызываемой форме; значение
ЁЁ PARAMETER.pr_commit_mode установите соответственно.
ЁЁ
ЁЁ Обратите внимание, что я всего лишь добавил параметр
ЁЁ
"commit mode" к
списку параметров, который передается
ЁЁ
процедуре callform. Здесь я предполагаю, что список
ЁЁ
параметров изменялся, и значение pr_commit_mode уже
ЁЁ переопределено.
*/
IF NAME_IN('SYSTEM.FORM_STATUS')
= 'CHANGED' OR
NAME_IN('PARAMETER.pr_commit_mode') = 'POST'
THEN
ADD_PARAMETER(plist_id_in,'pr_commit_mode',
TEXT_PARAMETER,'POST');
ELSE
ADD_PARAMETER(plist_id_in,'pr_commit_mode',
TEXT_PARAMETER,'COMMIT');
END IF;
/*
ЁЁ Добавим текущую форму к списку вызванных форм, и добавим
ЁЁ этот
параметр для передачи следующей форме
*/
ADD_PARAMETER(plist_id_in,'pr_called_forms',TEXT_PARAMETER,
NAME_IN('PARAMETER.pr_called_forms')|| '|' ||
GET_APPLICATION_PROPERTY(CURRENT_FORM_NAME));
/*
ЁЁ
И, наконец, настало время выполнить оператор CALL_FORM.
ЁЁ Заметьте, что я аккуратно пересылаю числовые константы,
ЁЁ значения которых заданы пользователем при обращении к
ЁЁ процедуре callform.
*/
CALL_FORM(fname_in, hide_in, replace_in, query_in, plist_id_in);
/*
ЁЁ Убедимся, что форма была вызвана. Можно составить более
ЁЁ
длинное сообщение; здесь же важно только удостовериться.
*/
IF NOT FORM_SUCCESS
THEN
MESSAGE (' Невозможно вызвать форму '|| fname_in);
END IF;
-- В случае "жесткого выхода"
необходимо заменить
--
значение уровня сообщений.
post_callform(msglvl);
END;
END lib_navigate;
Пакет процедур lib_navigate
является заменой "нижнего уровня" для
CALL_FORM. Как Вы можете видеть, это более чем
простой вызов
формы. Он поддерживает выдачу операторов post-commit, "жесткий
выход",
обновление экрана (которое в противном будет доставлять
Вам
беспокойство при каждом переключении контекста формы).
Конечно, этот пакет не обеспечивает передачу
данных между
формами. Я могу вызвать свою форму с помощью процедурного
интерфейса, но как я перешлю параметры
вызванной форме ?
И когда lib_navigate.callform
возвращает моей форме управление
(пользователь выходит из вызванной формы тем
или иным способом),
как восстановить переданную назад информацию ? Понятно, что нам
нужно больше, чем lib_navigate.callform
в вызывающей форме, если
мы желаем обеспечить стандартный способ
передачи информации между
формами.
Тема
№7. Передача Данных к Вызываемой Форме и от нее.
В SQL*Forms версии
3.0 имелся только один способ передачи
информации как при запуске формы, так и при возврате из
формы, а
именно с помощью глобальных переменных. Это
очень гибкий подход,
предлагающий крайне необходимую и очень
функциональную
возможность: глобальные переменные обеспечивают
передачу
информации не только между формами, но также
между формами и
меню.
Недостатком же глобальных переменных является то, что они
совершенно не структурированы. Не существует
простого способа
узнать, взглянув на определение формы, какие из
переменных
обеспечивают запуск формы, а какие - возвращают
значения, когда
форма закрыта. Разработчик просто должен знать,
какие глобальные
переменные нужно определять перед вызовом
формы, что делать с
ними в вызванной форме, на что должны оказывать
влияние эти
переменные при выполнении формы, и когда нужно
устанавливать
значения этих глобальных переменных.
Oracle Forms практически
решает эту проблему. Для этого в нем
имеется возможность еще на стадии разработки
определить параметры
формы. Тогда же Вы можете присвоить им значения
по умолчанию. Вы
также можете передать новые значения через
список параметров при
вызове формы с помощью CALL_FORM; можете изменить
значения
параметров во время выполнения формы. Параметры Oracle Forms
предоставляют намного более структурированный
способ передачи
информации к вызываемой форме. К несчастью, даже в Oracle
Forms
для передачи данных назад в вызывающую форму
приходится
полагаться на глобальные переменные.
Одним из наиболее важных уроков при построении
программы общего
назначения, подобной пакету lib_navigate,
является то, что это
стопроцентно универсальная программа. lib_navigate.callform -
процедура, которая полностью независима от
любых приложений или
структуры данных. Это очень хорошая замена для CALL_FORM, почти
безупречная. Я верю, что, когда Вам понадобится
разработать
программу типа lib_navigate.callform,
для Вас не составить
большого труда разработать общее решение.
В следующем примере, взятом из нашей системы
регистрации
телефонных
сообщений (префикс "ctr" в имени пакета
составлен из
первых букв названия системы "call tracking system"),
единственный пакет ctr_transfer
обеспечивает согласованный способ
вызова lib_navigate.callform. Эта программа определяет список
параметров для передачи процедуре callform и последующего
изучения значений соответствующих глобальных
переменных в случае,
если пользователь пересылал назад какую-либо
информацию.
PACKAGE ctr_transfer
IS
/*
ЁЁ Обеспечивает
интерфейс к двум "распахивающимся" экранам:
ЁЁ Caller (Клиент)
и Company
(Компания).
*/
FUNCTION
caller
(caller_id_inout IN OUT caller.caller_id_nu%TYPE,
caller_name_inout
IN OUT VARCHAR2,
type_cd_inout IN OUT caller.caller_type_cd%TYPE,
type_desc_inout IN OUT caller.caller_type_desc%TYPE
)
RETURN
BOOLEAN;
END ctr_transfer;
PACKAGE
BODY ctr_transfer IS
FUNCTION init_parameter_list (nm_in IN
VARCHAR2) RETURN PARAMLIST
IS
/*
ЁЁ Функция общего использования,
предназначенная для уничтожения
ЁЁ списка параметров, если он уже существует,
и последующего
ЁЁ возврата идентификационного номера
"обновленного" списка
ЁЁ параметров. Возможно, Вы захотите
поместить эту функцию в свою
ЁЁ стандартную библиотеку, поскольку она
может быть использована
ЁЁ во многих ситуациях.
*/
pl_id PARAMLIST;
BEGIN
pl_id := GET_PARAMETER_LIST (nm_in);
IF NOT ID_NULL(pl_id)
THEN
DESTROY_PARAMETER_LIST (nm_in);
END IF;
RETURN (CREATE_PARAMETER_LIST (nm_in));
END init_parameter_list;
FUNCTION
caller
(caller_id_inout IN OUT caller.caller_id_nu%TYPE,
caller_name_inout IN OUT VARCHAR2,
type_cd_inout IN OUT caller.caller_type_cd%TYPE,
type_desc_inout IN OUT caller.caller_type_desc%TYPE
)
RETURN BOOLEAN
/*
ЁЁ Возвращает TRUE,
если пользователь пересылает назад информацию
ЁЁ о клиенте из экрана Caller
(Клиент), в противном случае
ЁЁ возвращает FALSE.
*/
IS
pl_id PARAMLIST;
pn_string VARCHAR2(1000) :=
NULL;
first_name VARCHAR2(30);
last_name VARCHAR2(30);
retval BOOLEAN := FALSE;
BEGIN
-- Инициализация списка параметров
pl_id := init_parameter_list('xfer_caller');
/*
ЁЁ
Заполнение списка параметров. Я перехожу к экрану "Клиент"
ЁЁ
для одной из двух целей: либо посмотреть существующий
ЁЁ
список клиентов (выполнив запрос), либо ввести нового
ЁЁ
клиента. В первом случае достаточно переслать
ЁЁ идентификационный номер клиента (являющийся первичным
ЁЁ ключом в таблице клиентов). Для ввода нового клиента
ЁЁ
необходима некоторая информация о клиенте, которая
ЁЁ
пересылается через параметры процедуры. Заметьте, что все
ЁЁ
параметры характерны для клиента. Более общие
параметры
ЁЁ
поддерживаются процедурой lib_navigate.callform.
*/
IF caller_id_in IS
NOT NULL
THEN
-- Пересылка только идентификационного номера
ADD_PARAMETER(pl_id,'pr_caller_id_nu',
TEXT_PARAMETER, TO_CHAR(caller_id_in));
ELSE
-- Пересылка группы значений
lib_parse_name(caller_name_in,
first_name, last_name);
ADD_PARAMETER(pl_id,'pr_caller_id_nu',
TEXT_PARAMETER, NULL);
ADD_PARAMETER(pl_id,'pr_caller_last_name',
TEXT_PARAMETER, last_name);
ADD_PARAMETER(pl_id,'pr_caller_first_name',
TEXT_PARAMETER, first_name);
ADD_PARAMETER(pl_id,'pr_caller_type_cd',
TEXT_PARAMETER, type_cd);
ADD_PARAMETER(pl_id,'pr_caller_type_desc',
TEXT_PARAMETER, type_desc);
ADD_PARAMETER(pl_id,'pr_company_id_nu',
TEXT_PARAMETER, TO_CHAR(pr_company_id_nu_in));
ADD_PARAMETER(pl_id,'pr_company_name',
TEXT_PARAMETER, company_name_in);
END IF;
/*
ЁЁ
Список параметров определен. Пора вызывать форму. Сейчас
ЁЁ
мы переходим к очень специфическому коду пересылки данных.
ЁЁ
Глобальная переменная "ctr_caller_id_nu"
будет использована
ЁЁ в
экране "Клиент" для пересылки назад информации о
ЁЁ
выбранном клиенте.
Перед вызовом экрана я удаляю
ЁЁ
глобальную переменную. Когда управление
возвратится к этой
ЁЁ
процедуре, я проверю, не были ли определены эти глобальные
ЁЁ переменные.
*/
ERASE('GLOBAL.ctr_caller_id_nu');
lib_navigate.callform
('caller',NO_HIDE,DO_REPLACE,NO_QUERY_ONLY,pl_id);
DEFAULT_VALUE('NOT
SET', 'GLOBAL.ctr_caller_id_nu');
/*
ЁЁ В
случае, если установлен идентификационный номер клиента,
ЁЁ
пользователь желает ввести этого клиента, как персону, от
ЁЁ которой поступил телефонный звонок.
*/
IF :GLOBAL.ctr_caller_id_nu != 'NO SET'
THEN
/*
ЁЁ Передача информации о новом клиенте для обновления
ЁЁ записи, от которой был сделан вызов формы. Используется
ЁЁ идентификационный номер для заполнения полей экрана.
*/
:cal.caller_id_nu := TO_NUMBER(:GLOBAL.ctr_caller_id_nu);
ctr_fetch.caller
(caller_id_nu_inout,
last_name,
first_name,
caller_type_cd_inout,
caller_type_desc_inout);
caller_name_inout := last_name||',
'||first_name;
RETURN TRUE;
END
IF;
/*
ЁЁ За собой нужно прибрать:
ЁЁ
1. Удалить глобальные переменные, чтобы позднее они не
ЁЁ явились причиной
неправильного выполнения другой
ЁЁ программы.
ЁЁ
2. Уничтожить список параметров. Теоретически в этом нет
ЁЁ нужды, поскольку список
параметров будет уничтожен сразу
ЁЁ перед вызовом формы. То, что параметры не были очищены
ЁЁ раньше, иногда может
оказаться причиной неожиданного
ЁЁ результата работы Oracle
Forms.
*/
ERASE ('GLOBAL.ctr_caller_id_nu');
DESTROY_PARAMETER_LIST ('xfer_caller');
END caller;
END ctr_transfer;
Используя пакет ctr_transfer,
можно легко создавать кнопки в
линейке инструментальных средств, которые
позволят мгновенно
получать доступ к информации о клиентах и
компаниях.
Триггер When-Button-Pressed для экрана Callers (Клиенты):
IF ctr_transfer.caller (:call.caller_id_nu, :call.caller_name,
:call.caller_type_cd, :call.caller_type_desc)
THEN
/*
ЁЁ Используйте <встроенную процедуру> SET_ITEM_PROPERTY
ЁЁ
для изменения статуса элемента на VALID. Этот способ
ЁЁ
позволяет обойти требующие подтверждения (OK) сообщения.
*/
SET_ITEM_PROPERTY('call.caller_name',ITEM_IS_VALID,PROPERTY_ON);
SET_ITEM_PROPERTY('call.caller_type_desc',ITEM_IS_VALID,
PROPERTY_ON);
END IF;
Триггер When-Button-Pressed для экрана Companies (Компании):
IF ctr_transfer.company (:call.company_id_nu, :call.company_name,
:call.company_type_cd, :call.company_type_desc)
THEN
SET_ITEM_PROPERTY('call.company_name',ITEM_IS_VALID,PROPERTY_ON);
SET_ITEM_PROPERTY('call.company_type_desc',ITEM_IS_VALID,
PROPERTY_ON);
END IF;
При желании обеспечить простой способ
распахивать экраны с
помощью функциональных клавиш можно создать
процедуру, в основном
используя приведенный выше код, которая будет
просто вызываться
как триггерами When-Button-Pressed,
так и функциональной
клавишей.
Применяя этот способ, я снижаю до абсолютного минимума
избыточность кода в своих формах:
ctr_zoom_in('caller');
или
ctr_zoom_in('company');
Может быть, Вам кажется, что здесь уже слишком
много уровней? Да,
здесь слишком много уровней, но использование
каждого из них
может быть оправдано. Одна из причин держать
процедуру
ctr_transfer.caller отдельно от ctr_zoom_in
- это то, что
ctr_transfer.caller не зависит ни от какого конкретного блока или
формы. Я
мог бы использовать процедуру ctr_transfer.caller в
любой форме
для передачи управления к форме "Клиент" (например,
из экрана "История вызовов" или из
проблемного модуля).
Процедура ctr_zoom_in,
как показано выше, содержит ссылки на
особые элементы в блоке "call" ("Телефонный вызов"). Она также
устанавливает
реквизиты двух элементов в VALID (т.е.
действительный, правильный), что необходимо для входных
данных
формы call
("Телефонный вызов"), но может быть ненужным для
других экранов. Итак, я полагаю, что полностью
себя оправдал.
Преимущество использования множества этих
уровней заключается в
том, что это дает Вам и другим пользователям
возможность более
гибкого применения этих подпрограмм посредством
их модификации с
целью приспособления к особым требованиям. При этом накладные
расходы, связанные с вызовом одной процедуры из
другой,
незначительны.
Итак, мы только что увидели, что объединение
общей процедуры
lib_navigate.callform с набором специфических,
но последовательно
структурированных
функций ctr_transfer (и даже более
специфической процедуры ctr_zoom_in)
предлагает мощный способ
вызова любой формы и управления возвращаемой
информацией. А
сейчас взгляните на фрагмент программы,
необходимый в вызываемой
форме для логического завершения этого
интерфейса.
Тема
№8 .Инициализация Вызываемой Формы.
Триггер When-New-Form-Instance
(WNFI) активизируется при первом
вызове формы. В этот момент все параметры формы имеют
либо свои
значения по умолчанию, либо значения,
переданные в списке
параметров
при выполнении процедуры CALL_FORM (встроенной, в
нашем случае, в процедуру
lib_navigate.callform). Триггер WNFI
используется для модификации поведения формы
при ее запуске.
Обычно я использую триггер WNFI для управления следующими
сценариями:
Запуск формы по умолчанию Поведение формы, когда она запускается
(Default form startup) прямо из меню, без "контекста".
Этот
режим запуска формы
по умолчанию может
автоматически
выполнять запрос,
возвращающий
все записи, или просто
обеспечивать пустую
запись для ввода
данных.
Запрос, возвращающий Первичный ключ, переданный через
особую
запись параметр формы (или
глобальную
(Query up a
specific
переменную, хотя я уже давно не
record) использую этот метод), как
показано
выше в процедуре ctr_transfer.
Установка новой записи Информация для новой записи переслана
для
ввода через параметры
формы (снова взгляните
(Set up a
new record for для примера на процедуру ctr_transfer).
entry) Создается новая запись с
этими
значениями,
размещенными в
соответствующих полях, и пользователю
разрешается
выполнить ввод данных.
Я продолжу
примером экрана "Клиент", вызываемом из экрана
"Телефонные вызовы" (я это нарочно,
да?) процедурой ctr_transfer.
Эта форма имеет, вдобавок к стандартному списку
параметров,
описанному выше (см. "Шаг 0"), набор
параметров, характерных для
клиента:
Имя Параметра Описание Параметра
PR_caller_id_nu Содержит первичный ключ клиента.
Значение по
умолчанию NULL.
Устанавливается
только если
пользователь хочет
получить доступ к
информации о
клиенте, который уже был
выбран
из другого экрана.
PR_caller_first_name Содержит первое имя клиента, которое
пользователь хочет
определить.
Значение по
умолчанию NULL. Если
значение PR_caller_id_nu не пустое
(NOT NULL), то этот
параметр
игнорируется.
PR_caller_last_name Содержит последнее имя клиента,
которое
пользователь хочет определить.
Значение по
умолчанию NULL. Если
значение PR_caller_id_nu не пустое
(NOT NULL), то этот
параметр
игнорируется.
PR_caller_type_cd Содержит тип кода клиента, которое
пользователь хочет
определить.
Значение по умолчанию
NULL. Если
значение PR_caller_id_nu не пустое
(NOT NULL), то этот
параметр
игнорируется.
PR_caller_type_ds Содержит описание типа кода клиента,
которое
пользователь хочет определить.
Значение по
умолчанию NULL. Если
значение PR_caller_id_nu не пустое
(NOT NULL), то этот
параметр
игнорируется. Это описание можно
будет получить из
кода, но если оно у
меня уже есть, то
почему не
использовать его?
Эти два триггера нужны для управления запуском
формы, как
показано ниже:
1. Триггер на уровне формы When-New-Form_Instance:
/*
ЁЁ Если мне известен
идентификационный номер клиента, или если
ЁЁ режим запуска формы установлен "Query", то выполнить запрос
ЁЁ
(неограниченный или, напротив, только для известного
ЁЁ идентификационного номера).
*/
IF :PARAMETER.pr_caller_id_nu IS NOT NULL OR
:PARAMETER.pr_startup_mode = 'EXEQRY'
THEN
EXECUTE_QUERY;
/*
ЁЁ Если у меня есть
последнее имя клиента или если режим запуска
ЁЁ "New Record", то взять значения из параметров и вставить
ЁЁ нового клиента.
*/
ELSIF : :PARAMETER.pr_caller_last_name
IS NOT NULL OR
:PARAMETER.pr_startup_mode = 'CREREC'
THEN
-- Создаем новый чистый бланк для ввода информации
CREATE_RECORD;
/*
ЁЁ Передадим значения от параметров в поля для этого нового
ЁЁ клиента
*/
:caller.caller_first_name := :PARAMETER.caller_first_name;
:caller.caller_last_name := :PARAMETER.caller_last_name;
:caller.caller_type_cd := :PARAMETER.caller_type_cd;
:caller.caller_type_desc := :PARAMETER.caller_type_ds;
END IF;
2. Триггер на уровне блока Pre-Query
в блоке Caller ("Клиент").
Этот
триггер активизируется перед выполнением запроса. Если
параметр, содержащий идентификатор клиента, не пустой, это
значение присваивается первичному ключу клиента, в результате
чего
будет выбрана только та единственная запись, которую и
запрашивал пользователь через функцию ctr_transfer.
IF :PARAMETER.pr_caller_id_nu IS NOT NULL
THEN
:caller.caller_id_nu := :PARAMETER.caller_id_nu;
/*
ЁЁ Обнулить параметр, чтобы при выполнении следующего
ЁЁ
запроса пользователь не получал данные о том же
ЁЁ клиенте.
*/
:PARAMETER.pr_caller_id_nu := NULL;
END IF;
После того, как триггеры запустили форму в
соответствии с
предъявленными требованиями, пользователи могут
запросить
информацию о другом клиенте, создать нового
клиента, или
выполнить любое другое действие, разрешенное в
этой форме.
Следующая вещь, которую пользователю захочется
сделать, это
сохранить или отменить изменения. Сохранение в вызываемой форме
будет происходить одним из двух способов: Post или Commit.
Посмотрим теперь, как автоматически определить,
какой из двух
способов доступен.
Тема
№9. Сохранение Изменений в Вызываемой Форме.
Цель моего процедурного интерфейса - чтобы
разработчики никогда в
своих программах не
выполняли явно COMMIT_FORM или POST. Вместо
этого они должны вызвать процедуру и запросить
выполнение
сохранения. Процедура должна сама определить,
какого рода
сохранение должно иметь место.
В каком месте должна быть вызвана эта
процедура? Допустим, что
пользователь может запросить сохранение (неявное или явное)
одним из следующих способов:
Кнопка "Сохранить" Кнопка из линейки вызова команд, или
(Save button) расположенная в другом месте экрана,
которая
обеспечивает сохранение изменений
и возврат курсора в
текущий элемент.
Фиксирующее Действие Это опция DEFAULT-меню, также
сохраняющая
(Action-Commit) изменения.
Ключ "Фиксировать" Этот функциональный ключ делает то же
Key-Commit
самое, что и кнопка "Сохранить".
Кнопка "OK" Другая кнопка, которая сохраняет
любые
Сохранить и Выйти ожидающие этого изменения и затем
(OK button
закрывает форму.
Для Windows это обычная
Save and
Close) кнопка "OK".
Кнопка "Выход" При использовании кнопки
"Выход" (Exit)
Exit button или "Отказ" (Cancel) может быть предложено
отменить или зафиксировать
изменения.
Ваш стандартный выход
может отличаться
тем, что форма не будет
требовать
подтверждения
отмены/отката, а просто
произойдет выход из
формы.
Действие-Выход Эта опция в меню по умолчанию
(DEFAULT
(Action-Exit)
menu) обеспечивает выход из формы,
предварительно предложив
пользователю
сохранить изменения,
если такие были. Это
хороший выбор для
"жесткого выхода" из
формы.
Ключ "Выход" Эта функциональная клавиша выполняет
(Key-Exit) то же действие, что и пункт меню
Action-Exit
("жесткий выход").
Во многих случаях сохранение выполняется
непосредственно перед
выходом из экрана. Эта функциональная
возможность найдет
отражение в разрабатываемой нами процедуре. Перво-наперво нам,
однако, понадобится
процедура lib_save_changes, которая выполняет
пересылку (post) или
фиксацию (commit), как указано в
ее
единственном параметре.
Это параметр "режим фиксации" ("commit
mode"), который может может принимать одно из двух значений -
либо POST, либо COMMIT. Для поддержки режима фиксации мы
используем параметр pr_commit_mode. Этот параметр по умолчанию
принимает значение COMMIT в каждой форме, а
переопределяется он в
процедуре lib_navigate.callform
перед выполнением CALL_FORM.
PROCEDURE
lib_save_changes (mode_in
IN VARCHAR2 := 'COMMIT',
show_message IN VARCHAR2 := 'MESSAGE')
IS
/*
ЁЁ Основная процедура, обеспечивающая
сохранение изменений.
ЁЁ Параметр mode_in
определяет, будет выполняться post или
ЁЁ commit. Параметр show_message
выполняет роль опции, которую
ЁЁ разработчики могут использовать для
подавления сообщения в
ЁЁ случае, если эта
процедура была вызвана, но никакие изменения
ЁЁ не сохранялись.
*/
form_changes
BOOLEAN :=NAME_IN('SYSTEM.FORM_STATUS')='CHANGED'
mode_in VARCHAR2(20) :=UPPER(mode_in);
BEGIN
If form_changes
THEN
If mode_in =
'COMMIT'
THEN
COMMIT_FORM;
-- Проверить FORM_SUCCESS, чтобы удостовериться в том,
-- что встроенная процедура завершилась без ошибок
lib_check_failure;
ELSIF mode_in =
'POST'
THEN
POST;
lib_check_failure;
ELSE
MESSAGE('Недействительный режим сохранения: '||mode_in);
END
IF;
ELSE
-- Вывести сообщение, если оно соответствует заданному
-- уровню сообщений.
IF TO_NUMBER(NAME_IN('SYSTEM.MESSAGE_LEVEL'))
< 5 AND
UPPER(show_message_in) = 'MESSAGE'
THEN
MESSAGE('Нечего сохранять');
END IF;
END IF;
END lib_save_changes;
Я могу использовать процедуру lib_save_changes во множестве
триггеров для сохранения. Вот как просто,
например, выглядит
вызов этой процедуры в триггере When-Button-Pressed:
lib_save_changed(:PARAMETER.pr_commit_mode);
Если Вы создадите триггер Key-Commit,
включающий точно такой же
код, то этот триггер и процедура lib_save_changes будут также
выполняться всякий раз, когда пользователь
нажимает клавишу
Key-Commit или выбирает Action-Commit в меню.
Я могу написать процедуру lib_save_changes
без параметров и
просто сослаться на параметр pr_commit_mode прямо в процедуре.
Это делает процедуру lib_save_changes
несколько менее гибкой,
хотя, и мой опыт показывает снова и снова, что
это - обычная
плата за выбор максимальной гибкости. Используя
описанную выше
реализацию процедуры, например, разработчики будут иметь
возможность выбирать для выполнения процедуру с
явным режимом
фиксации (commit mode):
lib_save_changes('post','NO_MESSAGE');
Этот способ позволяет разработчикам выборочно
пересылать
изменения и спокойно использовать стандартную библиотечную
процедуру.
Вы сохраняете проект от "загибов" разработчиков;
кроме этого, если Вы когда-нибудь дополните
процедуру
lib_save_changes некоторыми новыми функциональными
возможностями
(сигнальным
прямоугольником (alert box),
например, который будет
более подробно показывать аспекты сохранения),
то даже свернувшие
с истинного пути (используя pr_commit_mode),
извлекут пользу.
Сейчас мы решили проблему сохранения данных с
помощью post или
commit, и это позволяет нам обратить внимание на последний
элемент архитектуры: выход из вызываемой формы.
Тема
№10. Закрытие и Выход из Вызываемой Формы.
В предыдущем разделе я обозначил четыре
различных пути выхода из
формы, а именно:
- путем нажатия на кнопку OK для сохранения и
закрытия формы;
- путем нажатия на кнопку Exit
для отмены изменений и выхода из
формы;
- путем выбора пункта меню Action-Exit;
- и, наконец, путем нажатия клавиши Exit, чтобы выполнить
"жесткий выход".
В некоторых из этих опций мы будем использовать
процедуру
lib_save_changes; однако во всех
опциях нам будет нужна
дополнительная процедура поддержки выхода из
формы. Программа
поддержки выхода имеет три основных
обязанности:
1. Сохранение перед выходом, если это нужно.
2. Пересылка значений назад
(или подготовка к сбросу установок
глобальных переменных для пересылки назад) в вызывающую форму.
3. Закрытие формы.
Мы будем использовать процедуру lib_save_changes для управления
(1). Задача (3) является вариантом EXIT_FORM с
некоторым кодом
для обеспечения "жесткого выхода",
который "выметает"
пользователя из любых вложенных форм. Пересылка данных в (2)
представляет наиболее интересную проблему. Я
хочу построить
общеупотребительную процедуру для управления
закрытием формы,
к которой буду обращаться сразу по выполнении post/commit и
вызова формы. Но пересылка данных имеет место в
очень многих
приложениях и даже между экранами. В одном экране я захочу
переслать назад идентификатор клиента. В другом я возвращаю
идентификационный номер компании. В третьей форме я не хочу
возвращать ничего. А в четвертой форме мне нужно возвратить
четыре различных значения. Как же я смогу интегрировать
обобщенную процедуру со специфическими
требованиями формы,
сохранив свою идею процедур многократного
использования?
Чистейшее решение основано на имеющейся
возможности задавать
порядок, в котором библиотеки присоединяются к
форме. Две разные
библиотеки могут содержать программы с
одинаковыми именами. Когда
Oracle Forms встречается
ссылка на эту программу, он ищет ее в
присоединяемых библиотеках
в заданном порядке, пока не найдет
первую подходящую. Следовательно, Вы можете изменять программу,
которая будет выполняться, манипулируя списком
библиотек,
присоединямых к Вашим формам. Как это может помочь Вам соединить
общую программу и специфический код?
Обсудим сценарий выхода из формы. В некоторых случаях сразу
перед выполнением EXIT_FORM Вы пожелаете
создать одну или больше
глобальных переменных для пересылки информации
назад в вызывающую
форму.
Это назначение должно иметь место внутри общей процедуры
lib_close_form. Построим
это средство по этапам и посмотрим,
какое преимущество может дать использование
этой библиотеки.
Во-первых, полностью универсальная процедура lib_close_form: эта
программа даже не пытается пересылать данные
назад в вызывающую
форму никаких глобальных переменных. Она просто сохраняет
изменения, если это нужно, и затем выходит из
формы. Она
хранится в стандартной библиотеке и вызывается
всеми формами
приложения. Ни для одной формы не делается
локальных копий этой
процедуры с целью модификации для персонального
использования.
PROCEDURE
lib_close_form
(action_in
IN VARCHAR2, mode_in IN VARCHAR2 := 'COMMIT')
IS
/*
ЁЁ Универсальная процедура закрытия формы.
Правильные значения
ЁЁ action_in:
ЁЁ OK - Сохранить и закрыть
ЁЁ CANCEL - Отменить изменения и закрыть
ЁЁ EXIT - Отменить изменения и выйти из всех
вызванных
ЁЁ форм.
*/
action_int VARCHAR2(5) := UPPER(action_in);
mode_in VARCHAR2(5) :=
UPPER(mode_in);
BEGIN
IF action_int = 'OK'
THEN
-- Здесь мы используем нашу процедуру сохранения изменений.
lib_save_changes(action_in,
'NO_MESSAGE');
/*
ЁЁ Если статус формы CHANGED, то что-то неправильно в
ЁЁ
процедуре lib_save_changes, и тогда забудьте о
EXIT_FORM.
*/
IF
:SYSTEM.STATUS = 'CHANGED'
THEN
MESSAGE('Ошибка сохранения изменений; выход отменен');
ELSE
-- Мы не сделали commit и не хотим делать rollback.
EXIT_FORM(NO_COMMIT, NO_ROLLBACK);
END IF;
--
ELSIF action_int = 'CANCEL'
THEN
--
Закрытие формы без проверки и с ограниченным rollback.
EXIT_FORM(NO_VALIDATE, TO_SAVEPOINT);
--
ELSIF action_int = 'EXIT'
THEN
/*
ЁЁ Устанавливая эту глобальную
переменную, я включаю режим
ЁЁ
"жесткого выхода", поскольку по возвращении управления
ЁЁ
процедуре lib_navigate.callform, будет проверена эта
ЁЁ
глобальная переменная и, если она установлена в "YES",
ЁЁ
будет запущен непосредственно другой EXIT_FORM.
*/
:GLOBAL.lib_exit_application := 'YES';
EXIT_FORM(NO_VALIDATE);
END IF;
END lib_close_form;
Сейчас процедура lib_close_form
поддерживает все требуемые
способы сохранения и выхода, но этого
совершенно недостаточно,
поскольку на самом деле требуется детальный
учет особенностей
формы для пересылки назад данных. Чтобы сделать lib_close_form
более гибкой, я вставлю вызов процедуры -
"болванки" ("dummy")
перед
EXIT_FORM в опции OK (чтобы установить значения для
пересылки) и в опции Cancel
(с целью удаления значений для
пересылки):
PROCEDURE
lib_close_form
(action_in
IN VARCHAR2, mode_in IN VARCHAR2 := 'COMMIT')
IS
action_int VARCHAR2(5) := UPPER(action_in);
mode_in VARCHAR2(5) :=
UPPER(mode_in);
BEGIN
IF action_int = 'OK'
THEN
lib_save_changes(action_in,
'NO_MESSAGE');
IF :SYSTEM.STATUS =
'CHANGED'
THEN
MESSAGE('Ошибка сохранения изменений; выход отменен');
ELSE
lib_set_data_transfer
('OK',GET_APPLICATION_PROPERTY(CURRENT_FORM_NAME));
EXIT_FORM(NO_COMMIT,
NO_ROLLBACK);
END IF;
--
ELSIF action_int = 'CANCEL'
THEN
lib_set_data_transfer
('CANCEL',GET_APPLICATION_PROPERTY(CURRENT_FORM_NAME));
EXIT_FORM(NO_VALIDATE,
TO_SAVEPOINT);
--
ELSIF action_int = 'EXIT'
THEN
:GLOBAL.lib_exit_application := 'YES';
EXIT_FORM(NO_VALIDATE);
END IF;
END lib_close_form;
где процедура с именем lib_set_data_transfer
должна находиться в
той же библиотеке, что и процедура lib_close_form. Этот вариант
процедуры lib_set_data_transfer
выполняет только пустой оператор:
PROCEDURE
lib_set_data_transfer
(action_in
IN VARCHAR2, form_in IN VARCHAR2)
IS
BEGIN
/*
ЁЁ Я расположил эту IF - END IF структуру в
процедуре, чтобы у
ЁЁ
разработчиков была готовая идея, как ее использовать (смотри
ЁЁ пример
использования ниже)
*/
IF UPPER(action_in) =
'OK'
THEN
-- Обычно Вы будете устанавливать глобальные
переменные
--
из значений локальных элементов.
IF form_in = 'здесь должно быть имя формы'
THEN
NULL;
END IF;
ELSIF UPPER(action_in) = 'CANCEL'
THEN
--
Обычно Вы будете УДАЛЯТЬ (ERASE) глобальные
--
переменные в этом разделе программы.
IF form_in = 'здесь
должно быть имя формы'
THEN
NULL;
END IF;
END IF;
END lib_set_data_transfer;
С такой добавкой к процедуре lib_set_data_transfer
процедура lib_close_form
будет выполняться точно так же, как
выполнялась до этого, т.е. данные назад не
пересылаются. А теперь
я создам новую библиотеку; пусть она называется
ctr_exc, как
сокращение
от Call TRacking EXCeption (Исключения Системы
Регистрации
Телефонных Сообщений). Эта библиотека содержит все
процедуры, которые я буду использовать как
инструмент обработки
исключений, заменяющий стандартный, однако не выбрасывая совсем
стандартную библиотеку. Процедура lib_set_data_transfer в этой
библиотеке значительно отличается от своего
аналога:
PROCEDURE
lib_set_data_transfer
(action_in
IN VARCHAR2, form_in IN VARCHAR2)
IS
BEGIN
IF UPPER(action_in) = 'OK'
THEN
IF form_in =
'CALLER'
THEN
COPY (NAME_IN('caller.caller_id_nu'),'GLOBAL.caller_id_nu');
ELSIF form_in =
'COMPANY';
THEN
COPY (NAME_IN('company.company_id_nu'),'GLOBAL.company_id_nu');
END IF;
ELSIF UPPER(action_in) = 'CANCEL'
THEN
IF form_in =
'CALLER'
THEN ERASE('GLOBAL.caller_id_nu');
ELSIF form_in =
'COMPANY';
THEN ERASE('GLOBAL.company_id_nu');
END IF;
END IF;
END lib_set_data_transfer;
Затем я присоединяю библиотеку ctr_exc перед библиотекой,
содержащей lib_close_form
и lib_set_data_transfer.
Сейчас
(после
повторной генерации всех модулей приложения),
форма lib_close_form
при выполнении будет вызывать ctr_exc - версию процедуры
lib_set_data_transfer, и устанавливать необходимые
мне для
пересылки в вызывающую форму глобальные
переменные. Вот как
выглядят триггеры, использующие процедуру lib_close_form:
Триггер When-Button-Pressed для кнопки OK:
lib_close_form('OK',:PARAMETER.pr_commit_mode);
Триггер When-Button-Pressed для кнопки Cancel:
lib_close_form('CANCEL',:PARAMETER.pr_commit_mode);
Триггер Key-Exit для
выполнения "жесткого выхода".
lib_close_form('EXIT');
Теперь у нас есть все компоненты, необходимые для всесторонней
навигации между формами и построения
коммуникационного
процедурного интерфейса:
lib_navigate.callform
Стандартная библиотека пакетов/процедур,
заменяющая
"родные" вызовы CALL_FORM;
определяет, что либо вызываемая форма
будет выполняться
только в режиме post,
либо в режиме полного commit.
ctr_transfer
Пакет, характерный для конкретного
приложения, который передает управление
от одной формы к следующей, используя для
этого процедуру lib_navige.callform;
контролирует
информацию, пересылаемую
назад из вызываемой
формы, и действия над
ней.
lib_save_changes
Стандартная библиотечная процедура,
заменяющая
"родные" обращения к POST и
COMIT_FORM.
Используется в различных
триггерах.
lib_close_form Стандартная библиотечная процедура,
заменяющая
"родное" обращение к
EXIT_FORM. Используется
в различных
триггерах.
Также вводит опцию "жесткий
выход".
lib_set_data_transfer
Стандартная библиотечная процедура или
заменяющая ее
процедура, характерная для
конкретного приложения,
которая
устанавливает значения глобальных
переменных для передачи
информации назад
в вызывающую форму.
Обеспечив разработчиков этими процедурами
взамен "родных"
встроенных процедур Oracle
Forms, мы оказываем влияние на
управление выполнением приложения и на окружение
системы
разработки. Мы облегчаем индивидуальному
разработчику приложения
процедуру следования сложному управлению
транзакциями при
навигации между формами и предлагаем
дополнительные,
нетривиальные функциональные возможности для их
форм. Конечно,
необходимо обучить разработчиков следованию
этим правилам,
запрещающим выполнять свои собственные COMMIT,
CALL_FORM и
EXIT_FORM.
Но если программы, которыми мы обеспечили
разработчиков, экономят их время - если
действительно мы
обеспечили шаблоны для этих вызовов в их
триггерах - они будут
использовать эти библиотеки и благодарить нас
за наши усилия.
Тема
№11. Несколько слов о NEW_FORM.
Вы можете просто добавить к пакету lib_navigate другую процедуру,
называющуюся newform, которая
будет управлять выполнением
встроенной процедуры NEW_FORM. Большинство
проблем и программ
идентичны описанным. Возможно, Вы хотите обсудить в паре с
процедурой lib_navigate.newform
параметр, позволяющий
разработчикам указывать, что они хотят после
выхода из вызываемой
формы вернуться в вызывающую форму. EXIT_FORM в этом случае не
будет выполняться как "return",
поскольку процедура NEW_FORM уже
закрыла вызывающую форму, но Вы можете
переслать имя вызывающей
формы через список параметров вызываемой форме;
затем, когда
пользователь будет выходить из вызываемой
формы, вместо простого
выполнения EXIT_FORM, Вы можете обратиться к
NEW_FORM, чтобы
снова восстановить вызывающую форму. Вы можете даже восстановить
запись, которая была отображена в этой форме,
сохранив в
глобальной переменной последний
выполнявшийся в вызывающей форме
запрос и выполнив его по возвращении. Возможно, такой стиль
навигации окажется для Вас желательным, даже
необходимым, если
Ваша оперативная память невелика, и по этой
причине Вы просто не
можете хранить одновременно слишком много форм открытыми.
Тема
№12. Преимущества Стандартного Процедурного Интерфейса.
При создании процедурного интерфейса, такого,
какой
обеспечивается пакетом lib_navigate,
Вы усиливаете Ваше
приложение несколькими способами:
1. Все разработчики применяют последовательное,
хорошо
разработанное и тщательно проверенное приближение к навигации
между
формами. Они не узнали всех сложностей, которые мы
только
что исследовали. Но они знают, где подучиться
техническим приемам, необходимым для управления процессами
пересылки данных в базу данных (post) и
фиксации изменений
(commit), и т.д. Продуктивность и контроль качества на этапе
разработки становится намного выше с того момента, как одни и
те же
программы начинают использоваться во многих формах.
2. В результате гарантируется однообразная
работа всех экранов;
пользователи знают, чего можно ожидать, и ценят
предсказуемость работы приложения.
Уверен, Вы не используете
ничего
похожего на пакет lib_navigate для навигации между
формами при построении экранов, которые все работали бы как
один.
Вы, конечно, можете задокументировать требуемый стиль
и
функциональные возможности, и позволить всем создавать большое
количество программ, не совсем одинаково работающих, для всех
Ваших
экранов, в которых могут повстречаться эти требования,
однако
это звучит не слишком вероятно.
3. При возникновении необходимости включения
изменений в проект
или по
требованию пользователя Ваше приложение может быть
легко расширено. Допустим, Вы забыли обеспечить обработку
редко встречающейся ситуации (и пользователь намекнул, что
комбинация, конечно, далека от правильной). Или
пользователи
решили, что они хотят видеть другое представление сигнального
бокса
при запросе на сохранения изменений, отличное от
стандартного сигнального бокса Oracle Forms "Do
you want to
commit changes?"
("Вы хотите зафиксировать изменения?").
Что бы
ни случилось, потребности Вашей программы
непосредственно и постоянно вызывают необходимость внесения
изменений.
При построении этого процедурного интерфейса
Вам достаточно
"сходить" в одно-два
места в Вашей библиотеке, создать и
затем проверить необходимые изменения и затем
сгенерировать.
Мгновенно все Ваши формы получают эти новые
возможности.
Очевидно, что преимущества этой техники могут
быть использованы
не только для навигации между формами. Всегда,
когда это подходит
и возможно, Вы будете конструировать
процедурный интерфейс для
общих областей использования функциональных
возможностей в Ваших
приложениях. Этот процесс является, фактически, способом
построения хорошего набора утилит и мощной
основы для библиотеки
шаблонов.
Об
авторе:
Стивен
Ферстайн является чикагским консультантом SSC и
основателем
и владельцем фирмы Artforms. Он разработал и написал
XRay Vision, отладчик SQL*Forms 3.0, версия которого доступна
членам IOUG в Compuserve
Oracle Forum. Стив
также написал
всплывающий
календарь для Oracle Forms
4. Он широко публиковался
в
группе пользователей Oracle и является постоянным
автором книг
по
программированию на Oracle для O'Reily
и Associates.
Steven Feuerstein
Artforms
voice: +1.312.262.8138
email 72053.441@compuserve.com