В процессе разработки в среде 1С:Предприятие часто возникает классическая задача взаимодействия интерфейсных объектов: из основной формы документа по кнопке открывается вспомогательная (произвольная) форма. В этой вспомогательной форме пользователь интерактивно заполняет данные, на основании которых прямо в процессе работы требуется произвести сложный расчет. При этом алгоритмы поиска данных и сами расчетные функции уже написаны и находятся в модуле основной формы.
Передать все данные заранее в момент открытия невозможно, так как расчет зависит от того, что именно пользователь выберет в дополнительном окне. В этой статье мы подробно разберем несколько способов решения этой проблемы как для обычных форм, так и для управляемых форм (УФ), а также рассмотрим архитектурно правильные методы проектирования логики в 1С.
Если вы работаете в обычном приложении, то самым простым и быстрым решением является использование свойства формы ВладелецФормы. Механизм платформы позволяет напрямую обращаться к экспортным процедурам и функциям той формы, которая открыла текущее вспомогательное окно.
Выполним эту задачу по шагам:
Экспорт. Это сделает её доступной извне.ПолучитьФорму).ВладелецФормы.Рассмотрим пример кода в основной форме документа:
// В модуле основной формы документа
Функция ВыполнитьРасчетНаОснованииДанных(ИДСпуска, ТаблицаПараметров) Экспорт
// Здесь выполняется сложный поиск по локальным переменным формы
// Например, поиск по табличной части "Листы"
НайтиЛист = Листы.НайтиСтроки(Новый Структура("ИДЛиста", ИДСпуска));
Если НайтиЛист.Количество() > 0 Тогда
Возврат НайтиЛист[0].ПечатнаяМашинаЛицо;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Теперь проанализируем, как вызвать эту функцию из модуля произвольной (вспомогательной) формы:
// В модуле произвольной (дополнительной) формы
Процедура КнопкаРассчитатьНажатие(Кнопка)
// Проверяем, что у формы задан владелец
Если ЗначениеЗаполнено(ВладелецФормы) Тогда
// Передаем параметры в функцию основной формы и получаем результат
ПечатнаяМашина = ВладелецФормы.ВыполнитьРасчетНаОснованииДанных(ТекущийИДСпуска, НастройкиПользователя);
// Используем полученный результат для дальнейших действий
Предупреждение("Расчет завершен. Выбранное оборудование: " + ПечатнаяМашина);
Иначе
ВызватьИсключение "Не удалось определить форму-владельца для выполнения расчета!";
КонецЕсли;
КонецПроцедуры
В управляемом интерфейсе (УФ) концепция прямого обращения через ВладелецФормы считается плохим тоном («жесткой связью»), поскольку вспомогательная форма становится полностью зависимой от контекста конкретной основной формы. Если форму-владельца переименуют или вы захотите открыть вспомогательное окно из другого документа, код выдаст ошибку.
Для реализации гибкого и независимого вызова по стандартам разработки 1С используется механизм обратного вызова (Callback) с помощью объекта ОписаниеОповещения. Давайте разберем этот паттерн:
В основной форме мы настраиваем открытие дополнительного окна и передаем процедуру, которая будет ожидать результат расчета:
&НаКлиенте
Процедура ОткрытьВспомогательнуюФорму(Команда)
// Описываем процедуру, которая выполнится ПОСЛЕ закрытия вспомогательной формы
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВспомогательнойФормы", ЭтотОбъект);
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("ТекущиеПараметры", Объект.Ссылка);
// Открываем форму, передавая ОписаниеОповещения последним параметром
ОткрытьФорму("Документ.НашДокумент.Форма.ВспомогательнаяФорма", ПараметрыФормы, ЭтотОбъект,,,, Оповещение, РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияВспомогательнойФормы(РезультатВыбора, ДополнительныеПараметры) Экспорт
Если РезультатВыбора <> Неопределено Тогда
// Здесь мы получаем данные из вспомогательной формы и запускаем расчет основной формы
ВыполнитьЛокальныйРасчетНаОснованииДанных(РезультатВыбора);
КонецЕсли;
КонецПроцедуры
Внутри вспомогательной формы нам не нужно знать ничего о структуре основной формы. Мы просто возвращаем результат расчетов при закрытии:
&НаКлиенте
Процедура КнопкаОКНажатие(Команда)
СтруктураРезультата = Новый Структура;
СтруктураРезультата.Вставить("ВыбранныйСпуск", Объект.ИДСпуска);
СтруктураРезультата.Вставить("Коэффициент", Объект.ПользовательскийКоэффициент);
// Закрываем форму и автоматически передаем данные в процедуру оповещения владельца
Закрыть(СтруктураРезультата);
КонецПроцедуры
С точки зрения архитектуры систем 1С, размещение сложных бизнес-алгоритмов и поисковых функций внутри модулей форм является ошибкой проектирования. Модуль формы должен отвечать исключительно за визуальное отображение элементов интерфейса и реакцию на действия пользователя.
Если расчетная функция требуется нескольким объектам или формам, ее необходимо вынести в модуль менеджера документа или в специализированный общий неконтекстный модуль. Рассмотрим, как это сделать:
Пример реализации функции в модуле менеджера документа:
// В Модуле менеджера документа
Функция РассчитатьПараметрыПечати(ДанныеДокумента, ТаблицаЛистов, ИДСпуска) Экспорт
// Логика полностью независима от формы. Все данные мы получили через параметры
НайтиЛист = ТаблицаЛистов.НайтиСтроки(Новый Структура("ИДЛиста", ИДСпуска));
Если НайтиЛист.Количество() > 0 Тогда
Возврат НайтиЛист[0].ПечатнаяМашинаЛицо;
КонецЕсли;
Возврат "Оборудование не найдено";
КонецФункции
Теперь из любой формы (хоть из основной, хоть из произвольной вспомогательной) вы можете легко вызвать этот метод:
// Вызов из вспомогательной формы
ТаблицаЛистов = ПолучитьТаблицуЛистовИзОсновнойФормы(); // Получаем данные
Результат = Документы.НашДокумент.РассчитатьПараметрыПечати(Объект.Ссылка, ТаблицаЛистов, Объект.ИДСпуска);
Часто расчетная функция требует передачи целых таблиц данных (например, временных таблиц значений, хранящихся в переменных формы). Посмотрим, как передать такие структуры.
Поскольку в обычном приложении весь код выполняется на клиенте в одном контексте, мы можем передавать тип ТаблицаЗначений напрямую, например, упаковав несколько таблиц в Массив или Структура:
// В модуле основной формы перед открытием вспомогательной
Процедура ОткрытьДополнительнуюФорму()
ПакетДанных = Новый Структура;
ПакетДанных.Вставить("ТаблицаРеквизитов", ТЗРеквизитыЛиста);
ПакетДанных.Вставить("ТаблицаОпераций", ТЗРеквизитыОперации);
ВспомогательнаяФорма = ПолучитьФорму("Документ.НашДокумент.Форма.ДополнительнаяФорма");
// Передаем пакет данных в реквизит вспомогательной формы
ВспомогательнаяФорма.ВременныйПакетДанных = ПакетДанных;
ВспомогательнаяФорма.Открыть();
КонецПроцедуры
В управляемом интерфейсе тип ТаблицаЗначений не существует на клиенте. Передавать большие объемы данных через серверные вызовы напрямую неэффективно. Лучший способ передачи по стандартам 1С — использование Временного хранилища:
// 1. В основной форме на сервере помещаем ТЗ в хранилище
&НаСервере
Функция ПоместитьТаблицыВХранилище()
ПакетДанных = Новый Структура;
// Преобразуем ТаблицыЗначений формы в обычные таблицы для сериализации
ПакетДанных.Вставить("Листы", РеквизитФормыВЗначение("ТЗРеквизитыЛиста"));
Возврат ПоместитьВоВременноеХранилище(ПакетДанных, УникальныйИдентификатор);
КонецФункции
// 2. Передаем полученный Адрес во вспомогательную форму в качестве параметра при открытии
Во вспомогательной форме на сервере мы сможем легко извлечь эти таблицы по переданному адресу и выполнить все расчеты в общем модуле.
Резюме: Для быстрого исправления в обычных формах используйте конструкцию ВладелецФормы.ИмяПроцедуры(). Для разработки масштабируемых систем на управляемых формах переносите расчетные алгоритмы в модули менеджеров объектов или общие модули, а взаимодействие между экранными формами стройте на асинхронных оповещениях ОписаниеОповещения.