Стивен Ферстайн.

 

 

 

 

Навигация и передача параметров между формами в

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.  Проектирование Элементов Формы.

Тема №6. Вызов Формы.

Тема №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

Эта глобальная переменная содержит имя формы, которая

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

приложения. Эта глобальная переменная может быть использована в

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

инициализирует установку метки формы, соответствующей кнопке

выхода, была ли она вызвана пользователем из экрана меню (в этом

случае метка называется "Выход"), или в экране, вызванном из

этого меню (тогда метка называется "Возврат в меню"). Эта

глобальная переменная является переменной на уровне приложения.

 

 

 

 

Тема №6. Вызов Формы.

 

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

(мои "пользователи") никогда не используют оператор 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

6759 N.Maplewood Ave.

Chicago, IL 60645

USA

voice: +1.312.262.8138

email 72053.441@compuserve.com

 



Используются технологии uCoz