В практике разработки на платформе 1С:Предприятие 8 часто возникает задача: одна и та же обработка должна использоваться в двух сценариях. Первый сценарий — интерактивный (например, под правами администратора), когда пользователю открывается форма для ручного задания параметров, сопоставления значений и нажатия кнопок. Второй сценарий — фоновый или пакетный (под обычным пользователем или регламентным заданием), когда обработка должна запуститься без показа интерфейса, выполнить алгоритм загрузки или расчета и сразу вернуть результат в вызывающий модуль.
Нередко разработчики пытаются решить эту задачу «в лоб»: вызывают метод ОткрытьФорму() с параметром автозапуска, а внутри обработчика события ПриОткрытии пытаются выполнить логику и закрыть форму с помощью метода Закрыть(Параметры) или установки флага Отказ = Истина. Однако при таком подходе форма либо зависает, либо вызывающая процедура оповещения получает Неопределено вместо реальных данных. Разберем по шагам, почему это происходит и как правильно решить задачу.
Проанализируем поведение управляемой формы на этапе её инициализации:
ПриОткрытии мы присваиваем параметру Отказ значение Истина, платформа прерывает процесс создания окна. Поскольку форма фактически так и не была открыта, событие её закрытия не наступает. Обработчик оповещения о закрытии (описание оповещения, переданное в параметрах метода ОткрытьФорму) либо не вызывается вовсе, либо возвращает Неопределено.ПриОткрытии контекст визуального окна еще формируется, и форма формально не имеет статуса открытой. Попытка вызвать метод Закрыть() на этом этапе игнорируется платформой управляемого приложения. В результате окно формы все равно выводится на экран, а параметры не возвращаются.Если код обработки жестко привязан к клиентскому контексту формы и переписать его быстро невозможно, рассмотрим способ корректного закрытия формы с возвратом результата сразу после её появления на экране. Для этого задействуем подключение однократного обработчика ожидания.
Идея заключается в том, чтобы дать форме штатно завершить процедуру ПриОткрытии, а само выполнение алгоритма и закрытие вынести в отдельную клиентскую процедуру, которая запустится с минимальной задержкой в 0.1 секунды.
Посмотрим на пример реализации в модуле формы открываемой обработки:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Проверяем параметр, переданный при открытии формы
Если Параметры.ВыполнитьСразу И НеОткрыватьФорму Тогда
// Подключаем однократный обработчик ожидания
ПодключитьОбработчикОжидания("СделатьИЗакрыть", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура СделатьИЗакрыть()
СтрокаОшибок = "";
МассивИмен = Новый Массив;
// Выполняем основную обработку данных
РезультатУспешен = ЗагрузитьДанные(МассивИмен, СтрокаОшибок);
// Формируем структуру с данными для возврата
СтруктураОтвета = Новый Структура;
СтруктураОтвета.Вставить("Успех", РезультатУспешен);
СтруктураОтвета.Вставить("СтрокаОшибок", СтрокаОшибок);
// Закрываем форму с передачей результата
Закрыть(СтруктураОтвета);
КонецПроцедуры
При таком подходе форма переходит в статус открытой, после чего обработчик ожидания СделатьИЗакрыть гарантированно отрабатывает, вызывает метод Закрыть(СтруктураОтвета), и платформа штатно передает результат в ОписаниеОповещения вызывающего кода.
Вариант с открытием и немедленным закрытием формы через обработчик ожидания является интерфейсным «костылем». Он заставляет платформу тратить ресурсы на создание оконных дескрипторов, элементов управления и передачу интерфейсных пакетов между сервером и клиентом. Кроме того, такой код невозможно использовать в регламентных заданиях, где пользовательский интерфейс отсутствует вовсе.
Разберем эталонную архитектуру разделения обязанностей:
Рассмотрим, как вынести бизнес-логику в модуль менеджера обработки. В качестве примера возьмем загрузку данных из внешнего источника через COM-соединение ADODB:
// В модуле менеджера обработки
Функция ЗагрузитьТЗИзПП(СменаИлиПериодВыборки, ТаблицаРезультата, СтрокаОшибки) Экспорт
СтрокаПодключения = "ваша_строка_подключения";
Connection = Новый COMОбъект("ADODB.Connection");
Connection.Open(СтрокаПодключения);
RS = Новый COMОбъект("ADODB.Recordset");
RS.ActiveConnection = Connection;
QueryText = "SELECT Shift.DateTimeOfBegin, ClientName.Name, Product.name, TRS.price " +
"FROM gas.dbo.CrdTransActRegister as TRS " +
"LEFT JOIN gas.dbo.CrdClients ClientName ON TRS.kClient = ClientName.kClient " +
"LEFT JOIN gas.dbo.products Product ON TRS.kPr = Product.kprCard";
RS.CursorType = 3;
RS.Open(QueryText);
Пока RS.EOF = 0 Цикл
НоваяСтрока = ТаблицаРезультата.Добавить();
Для Каждого Колонка Из ТаблицаРезультата.Колонки Цикл
НоваяСтрока[Колонка.Имя] = RS.Fields(Колонка.Имя).Value;
КонецЦикла;
RS.MoveNext();
КонецЦикла;
RS.Close();
Connection.Close();
// Анализируем соответствия в регистрах сведений
ВсеОк = Истина;
Для Каждого СтрТЗ Из ТаблицаРезультата Цикл
Отбор = Новый Структура("КодПП", СтрТЗ.КодКлиент);
СтрТЗ.КлиентСсылка = РегистрыСведений.ах_СоответствияКлиенты.Получить(Отбор).Контрагент;
Если Не ЗначениеЗаполнено(СтрТЗ.КлиентСсылка) Тогда
ВсеОк = Ложь;
СтрокаОшибки = СтрокаОшибки + Символы.ПС + "Не найдено соответствие для: " + СтрТЗ.Клиент;
КонецЕсли;
КонецЦикла;
Возврат ВсеОк;
КонецФункции
Теперь проанализируем, как организуется вызов из другой обработки или общего модуля. В зависимости от режима мы либо вызываем функцию напрямую, либо открываем интерфейс:
&НаСервере
Процедура ВыполнитьОперациюНаСервере(РежимАдминистратора)
Если РежимАдминистратора Тогда
// Для интерактивного режима передаем управление на клиент для открытия формы
Возврат;
Иначе
// Фоновый/автоматический режим: работаем напрямую с модулем менеджера
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("КодКлиент");
ТаблицаДанных.Колонки.Добавить("Клиент");
ТаблицаДанных.Колонки.Добавить("КлиентСсылка");
СтрокаОшибки = "";
Результат = Обработки.ЗагрузкаДанных.ЗагрузитьТЗИзПП(0, ТаблицаДанных, СтрокаОшибки);
Если Результат Тогда
// Создаем документы по полученной ТаблицеДанных
СоздатьДокументыПоТаблице(ТаблицаДанных);
Иначе
ЗаписьЖурналаРегистрации("Загрузка данных", УровеньЖурналаРегистрации.Ошибка,,, СтрокаОшибки);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Если же открытие формы всё-таки требуется администратору, на клиенте вызывается стандартный метод ОткрытьФорму, а в модуле самой формы заполнение таблицы части выполняется простой загрузкой полученной таблицы значений через метод ТабличнаяЧасть.Загрузить(ТаблицаДанных).
При реализации фоновых и клиент-серверных механизмов обратим внимание на следующие технические детали:
COMОбъект вызовет исключение. Также следует учитывать разрядность: 64-разрядный сервер 1С требует наличия 64-разрядного OLE DB провайдера базы данных.Обработки.ИмяОбработки.Создать() недоступен на тонком клиенте. Создание экземпляра объекта и вызов методов модуля объекта всегда должны выполняться на стороне сервера.Оповестить(ИмяСобытия, Параметры), который доставит результат в форму-приемник независимо от нюансов закрытия дочернего окна.