При переходе со старых конфигураций на обычных формах к современным решениям на управляемых формах (с использованием Библиотеки стандартных подсистем — БСП) разработчики часто сталкиваются с архитектурным барьером. В обычных формах процесс печати был линейным: прямо посреди формирования макета можно было вызвать метод ВыбратьЭлемент() или Вопрос(), приостановить выполнение кода, получить ответ пользователя и продолжить генерацию табличного документа.
В управляемых формах ситуация в корне меняется. Формирование печатных форм происходит на сервере (обычно в модуле менеджера объекта), где интерактивные действия и диалоги с пользователем физически невозможны и вызывают ошибку компиляции. В то же время стандартная подсистема печати БСП требует красивого вывода табличного документа в типовое окно предпросмотра, где пользователю доступны отправка по почте, сохранение в PDF и другие сервисные функции. Как же «спросить» пользователя о параметрах печати (например, выбрать тип акта или указать количество копий поддонов), не нарушая стандарты разработки БСП? Разберем это детально.
Чтобы решить эту задачу красиво и без отказа от стандартного окна предпросмотра БСП, мы должны разделить процесс на два этапа:
Рассмотрим пошагово реализацию этого механизма для встроенных и внешних печатных форм.
Представим ситуацию из обсуждения: при печати документа нам необходимо спросить у пользователя, какой вид акта формировать — «Акт передачи» или «Акт возврата». Выясним, как это запрограммировать.
В модуле менеджера документа в процедуре ДобавитьКомандыПечати мы регистрируем нашу команду. Вместо стандартного общего модуля печати, который сразу отправляет команду на сервер, мы укажем свой собственный клиентский обработчик.
Процедура ДобавитьКомандыПечати(КомандыПечати) Экспорт
КомандаПечати = КомандыПечати.Добавить();
КомандаПечати.Идентификатор = "АктПриемаПередачи";
КомандаПечати.Представление = НСтр("ru='Акт приема-передачи ОС';uk='Акт прийому-передачі ОЗ'");
// Вместо стандартного УправлениеПечатьюБПКлиент.ВыполнитьКомандуПечати
// указываем собственный клиентский общий модуль
КомандаПечати.Обработчик = "МойКлиентскийМодульПечатиКлиент.ВыполнитьПечатьСВыборомРежима";
КомандаПечати.СписокФорм = "ФормаСписка,ФормаВыбора,ФормаДокумента";
КонецПроцедуры
Теперь создадим неглобальный общий модуль МойКлиентскийМодульПечатиКлиент (свойства: «Клиент (управляемое приложение)»). В нем мы опишем процедуру, которая будет вызываться при нажатии кнопки на форме. Так как в управляемом клиенте все диалоги асинхронные, мы применим объект ОписаниеОповещения.
Процедура ВыполнитьПечатьСВыборомРежима(ПараметрКоманды, ПараметрыВыполненияКоманды) Экспорт
// ПараметрКоманды содержит массив ссылок на выбранные документы
Если ПараметрКоманды.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Формируем список выбора для диалога
СписокВыбора = Новый СписокЗначений;
СписокВыбора.Добавить("Передача", "Акт приема-передачи");
СписокВыбора.Добавить("Возврат", "Акт возврата");
// Конструируем параметры для передачи в обработчик оповещения
ДополнительныеПараметры = Новый Структура;
ДополнительныеПараметры.Вставить("ПараметрКоманды", ПараметрКоманды);
ДополнительныеПараметры.Вставить("Источник", ПараметрыВыполненияКоманды.Источник);
Оповещение = Новый ОписаниеОповещения("ПослеВыбораВидаАкта", ЭтотОбъект, ДополнительныеПараметры);
// Вызываем асинхронный выбор из списка на клиенте
ПоказатьВыборИзСписка(Оповещение, СписокВыбора, "Выберите вид акта для печати");
КонецПроцедуры
В этом же модуле пишем экспортную процедуру ожидания ПослеВыбораВидаАкта, которая сработает, когда пользователь выберет один из вариантов.
Процедура ПослеВыбораВидаАкта(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
Если ВыбранныйЭлемент = Неопределено Тогда
Возврат; // Пользователь закрыл окно выбора без выбора значения
КонецЕсли;
// Создаем структуру параметров печати и помещаем туда выбор пользователя
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("ВыбранныйВидАкта", ВыбранныйЭлемент.Значение);
// Вызываем стандартное окно печати БСП, передавая заполненную структуру
УправлениеПечатьюКлиент.ВыполнитьКомандуПечати(
"Документ.РеализацияТоваровУслуг", // Имя менеджера печати
"АктПриемаПередачи", // Идентификатор команды
ДополнительныеПараметры.ПараметрКоманды,
ДополнительныеПараметры.Источник,
ПараметрыПечати
);
КонецПроцедуры
Теперь возвращаемся в модуль менеджера документа РеализацияТоваровУслуг. Стандартный метод Печать принимает вторым параметром нашу структуру ПараметрыПечати. Нам остается лишь прочитать значение и направить алгоритм по нужному пути.
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
Если УправлениеПечатью.НужноПечататьМакет(КоллекцияПечатныхФорм, "АктПриемаПередачи") Тогда
// По умолчанию определяем вид акта
ВидАкта = "Передача";
// Считываем значение, переданное с клиента
Если ПараметрыПечати <> Неопределено И ПараметрыПечати.Свойство("ВыбранныйВидАкта") Тогда
ВидАкта = ПараметрыПечати.ВыбранныйВидАкта;
КонецЕсли;
// Формируем имя макета и заголовок в зависимости от выбора
Если ВидАкта = "Передача" Тогда
СинонимМакета = "Акт приема-передачи ОС";
ТабДок = СформироватьАктПередачи(МассивОбъектов, ОбъектыПечати);
Иначе
СинонимМакета = "Акт возврата ОС";
ТабДок = СформироватьАктВозврата(МассивОбъектов, ОбъектыПечати);
EndIf;
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(
КоллекцияПечатныхФорм,
"АктПриемаПередачи",
СинонимМакета,
ТабДок, ,
"Документ.РеализацияТоваровУслуг.Макет"
);
КонецЕсли;
КонецПроцедуры
Таким образом, мы полностью сохранили штатный механизм БСП: печатная форма выводится в красивое стандартное окно, поддерживается пакетная печать, при этом пользователь делает интерактивный выбор на клиенте перед генерацией отчета.
Рассмотрим более сложный пример, упомянутый в сообщении №17. Складским рабочим требуется распечатать этикетки для поддонов. При этом количество поддонов всегда разное, и программа должна запросить у пользователя число (например, «10»), чтобы сгенерировать 10 страниц с надписями «1/10», «2/10» ... «10/10».
Для решения этой задачи мы заменим выбор из списка на ввод числа.
Процедура ВыполнитьПечатьЭтикетокПоддонов(ПараметрКоманды, ПараметрыВыполненияКоманды) Экспорт
ДополнительныеПараметры = Новый Структура;
ДополнительныеПараметры.Вставить("ПараметрКоманды", ПараметрКоманды);
ДополнительныеПараметры.Вставить("Источник", ПараметрыВыполненияКоманды.Источник);
Оповещение = Новый ОписаниеОповещения("ПослеВводаКоличестваПоддонов", ЭтотОбъект, ДополнительныеПараметры);
// Запрашиваем у пользователя число с ограничением ввода
Подсказка = "Укажите количество поддонов для печати:";
ПоказатьВводЧисла(Оповещение, 1, Подсказка, 10, 2); // Начальное значение 1, разрядность 10, дробная часть 0
КонецПроцедуры
Процедура ПослеВводаКоличестваПоддонов(РезультатВвода, ДополнительныеПараметры) Экспорт
Если РезультатВвода = Неопределено Или РезультатВвода <= 0 Тогда
Возврат; // Пользователь отказался от ввода или ввел некорректное число
КонецЕсли;
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("КоличествоПоддонов", РезультатВвода);
УправлениеПечатьюКлиент.ВыполнитьКомандуПечати(
"Документ.РасходныйОрдерНаТовары",
"ЭтикеткиПоддонов",
ДополнительныеПараметры.ПараметрКоманды,
ДополнительныеПараметры.Источник,
ПараметрыПечати
);
КонецПроцедуры
В модуле менеджера документа формируем макет в цикле, опираясь на переданный параметр:
Функция СформироватьЭтикеткиПоддонов(МассивОбъектов, ОбъектыПечати, ПараметрыПечати)
ТабДок = Новый ТабличныйДокумент;
ТабДок.КлючПараметровПечати = "ПараметрыПечати_ЭтикеткиПоддонов";
КоличествоПоддонов = 1; // Значение по умолчанию
Если ПараметрыПечати <> Неопределено И ПараметрыПечати.Свойство("КоличествоПоддонов") Тогда
КоличествоПоддонов = ПараметрыПечати.КоличествоПоддонов;
КонецЕсли;
Макет = ПолучитьМакет("МакетЭтикетки");
ОбластьЭтикетки = Макет.ПолучитьОбласть("Этикетка");
ПервыйДокумент = Истина;
Для Каждого СсылкаНаДокумент Из МассивОбъектов Цикл
Для НомерПоддона = 1 По КоличествоПоддонов Цикл
Если Не ПервыйДокумент Тогда
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
ПервыйДокумент = Ложь;
// Заполняем параметры области макета
ОбластьЭтикетки.Параметры.Заполнить(СсылкаНаДокумент);
ОбластьЭтикетки.Параметры.НомерПоддонаТекст = Строка(НомерПоддона) + " / " + Строка(КоличествоПоддонов);
ТабДок.Вывести(ОбластьЭтикетки);
КонецЦикла;
КонецЦикла;
Возврат ТабДок;
КонецФункции
Если вы разрабатываете внешнюю печатную форму (ВПФ) и хотите спросить что-то у пользователя, вы не можете изменить общие модули или модуль менеджера типовой конфигурации. Но БСП предоставляет элегантное решение и для этого случая.
Проанализируем структуру внешней обработки и настроим ее для интерактивного взаимодействия.
В модуле объекта внешней обработки в функции СведенияОВнешнейОбработке для нашей команды необходимо установить особый тип использования: Вызов клиентского метода.
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработкеКликСистем();
// Задаем параметры регистрации (Вид, Назначение и т.д.)
// ...
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = "Акт с выбором параметров (внешний)";
НоваяКоманда.Идентификатор = "ВнешнийАктСПараметрами";
// КРИТИЧЕСКИ ВАЖНО: Указываем клиентский запуск
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовКлиентскогоМетода();
Возврат ПараметрыРегистрации;
КонецФункции
Поскольку команда имеет тип «Вызов клиентского метода», БСП при ее нажатии будет искать форму внешней обработки (которая должна быть назначена основной формой) и пытаться вызвать в ее модуле экспортную процедуру Печать.
Создадим основную форму внешней обработки и в ее модуле напишем код взаимодействия:
&НаКлиенте
Процедура Печать(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
// Мы находимся на клиенте в контексте формы нашей обработки.
// Можем открыть диалог или даже полноценную форму нашей обработки для ввода параметров!
ДополнительныеПараметры = Новый Структура;
ДополнительныеПараметры.Вставить("ОбъектыНазначения", ОбъектыНазначенияМассив);
Оповещение = Новый ОписаниеОповещения("ПослеВыбораПараметровВПФ", ЭтотОбъект, ДополнительныеПараметры);
СписокВыбора = Новый СписокЗначений;
СписокВыбора.Добавить("Полный", "Полный акт");
СписокВыбора.Добавить("Сокращенный", "Сокращенный акт");
ПоказатьВыборИзСписка(Оповещение, СписокВыбора, "Выберите детализацию акта:");
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораПараметровВПФ(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
Если ВыбранныйЭлемент = Неопределено Тогда
Возврат;
КонецЕсли;
// Формируем параметры для передачи на сервер печати БСП
ПараметрыОткрытия = Новый Структура;
// Заполняем обязательные свойства для БСП
ПараметрыОткрытия.Вставить("ИсточникПечати", "ДополнительныеОтчетыИОбработки");
ПараметрыОткрытия.Вставить("ПараметрыОтправки", Новый Структура("Получатель", ""));
// Передаем ссылку на саму внешнюю обработку и имя команды
ПараметрыОткрытия.Вставить("Ссылка", ЭтотОбъект.РеквизитФормыВЗначение("Объект").Ссылка);
ПараметрыОткрытия.Вставить("ИдентификаторКоманды", "ВнешнийАктСПараметрами");
ПараметрыОткрытия.Вставить("ОбъектыНазначения", ДополнительныеПараметры.ОбъектыНазначения);
// Передаем структуру наших собственных параметров
ДополнительныеПараметрыПечати = Новый Структура;
ДополнительныеПараметрыПечати.Вставить("ДетализацияАкта", ВыбранныйЭлемент.Значение);
ПараметрыОткрытия.Вставить("ПараметрыПечати", ДополнительныеПараметрыПечати);
// Открываем стандартную форму просмотра печатных документов БСП
ОткрытьФорму("ОбщаяФорма.ПечатьДокументов", ПараметрыОткрытия);
КонецПроцедуры
В модуле объекта нашей ВПФ в процедуре Печать мы принимаем переданные параметры и формируем макет:
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// Получаем переданные параметры из глобальных параметров БСП
ДетализацияАкта = "Полный"; // Значение по умолчанию
Если ПараметрыВывода.Свойство("ПараметрыПечати")
И ТипЗнч(ПараметрыВывода.ПараметрыПечати) = Тип("Структура") Тогда
Если ПараметрыВывода.ПараметрыПечати.Свойство("ДетализацияАкта") Тогда
ДетализацияАкта = ПараметрыВывода.ПараметрыПечати.ДетализацияАкта;
КонецЕсли;
КонецЕсли;
// Строим табличный документ согласно выбранному режиму
ТабДок = СформироватьВнешнийАкт(МассивОбъектов, ДетализацияАкта);
// Выводим в коллекцию
ПечатнаяФорма = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(КоллекцияПечатныхФорм, "ВнешнийАктСПараметрами");
Если ПечатнаяФорма <> Неопределено Тогда
ПечатнаяФорма.ТабличныйДокумент = ТабДок;
КонецЕсли;
КонецПроцедуры
Мы подробно разобрали, как обойти ограничение клиент-серверного разделения в 1С управляемых формах и сохранить при этом все преимущества интеграции с БСП. Главное правило — никогда не пытаться вызывать модальные или асинхронные окна на стороне сервера в процедурах печати модуля менеджера. Выносите всю интерактивную работу с пользователем на уровень клиентского обработчика команд, собирайте нужные данные и передавайте их на сервер через стандартный параметр ПараметрыПечати. Этот подход сделает вашу конфигурацию надежной, быстрой и полностью соответствующей стандартам разработки фирмы «1С».