При разработке внешних печатных форм (ВПФ) в среде 1С:Предприятие 8.3 на управляемых формах разработчики часто сталкиваются с задачей, когда пользователю требуется распечатать не весь документ целиком, а только одну или несколько выделенных строк табличной части. Например, в документе Установка цен номенклатуры или Реализация товаров и услуг может быть несколько сотен позиций, а на печать нужно отправить лишь выбранную позицию номенклатуры.
Попытки получить доступ к активной строке стандартными методами на сервере приводят к фатальной ошибке: "Интерактивные операции недоступны". В этой статье мы подробно разберем причины этой ошибки, выясним, почему обходной путь с использованием толстого клиента является устаревшим и опасным, и рассмотрим единственно верное архитектурное решение этой задачи по стандартам БСП (Библиотеки стандартных подсистем) для тонкого клиента.
Проанализируем классическую ошибку, которую совершают начинающие программисты 1С. По умолчанию стандартная команда печати внешней обработки выполняется на сервере. При этом на сервер передается только массив ссылок на выбранные документы ОбъектыНазначения.
Когда разработчик пытается внутри серверного метода получить текущую строку формы через конструкцию вида:
ВыдСтр = СсылкаНаОбъект.ПолучитьФорму("ФормаДокумента").Элементы.ДеревоЦен.ТекущиеДанные;
платформа 1С генерирует исключение. Это происходит по двум причинам:
ПолучитьФорму() является интерактивным. На сервере нет интерфейса (UI), поэтому вызов любых методов, связанных с отображением или получением экранных форм, там принципиально запрещен.ПолучитьФорму() без параметров пытается создать новое окно формы, а не возвращает уже открытую пользователем форму с заполненными данными и выделенной строкой. В результате разработчик получает пустой объект или ошибку обращения к контексту.Некоторые программисты решают эту проблему переходом на толстый клиент, где ограничения клиент-серверного взаимодействия размыты. Однако это тупиковый путь. Современные конфигурации (такие как Управление торговлей 11, ERP, Комплексная автоматизация) рассчитаны на работу в тонком и веб-клиенте. Работа в толстом клиенте снижает производительность системы, делает невозможным запуск базы через браузер и нарушает стандарты разработки фирмы "1С".
Чтобы корректно передать выделенную строку из формы документа во внешнюю печатную форму в тонком клиенте, необходимо использовать механизм БСП под названием "Вызов клиентского метода". Этот подход позволяет запустить выполнение алгоритма печати непосредственно на клиенте, перехватить контекст открытой формы документа и забрать из нее любые выделенные строки.
Рассмотрим по шагам, как реализовать этот механизм во внешней печатной форме.
Откроем модуль объекта нашей внешней печатной формы. В стандартной функции СведенияОВнешнейОбработке() найдем таблицу команд. Нам нужно настроить команду печати так, чтобы ее запуск происходил не на сервере, а на клиенте. Для этого используем тип запуска ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовClientМетода().
Разберем пример заполнения таблицы команд:
Функция СведенияОВнешнейОбработке(ПараметрыРегистрации) Экспорт
ПараметрыРегистрации = Новый Структура;
ПараметрыРегистрации.Вставить("Вид", "ПечатнаяФорма");
МассивНазначений = Новый Массив;
МассивНазначений.Добавить("Документ.УстановкаЦенНоменклатуры");
ПараметрыРегистрации.Вставить("Назначение", МассивНазначений);
ПараметрыРегистрации.Вставить("Наименование", "Печать выделенной строки номенклатуры");
ПараметрыРегистрации.Вставить("Версия", "1.0");
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь);
ПараметрыРегистрации.Вставить("Информация", "Печать только выбранных позиций в табличной части.");
// Создаем таблицу команд
ТаблицаКоманд = ПолучитьТаблицуКоманд();
НоваяКоманда = ТаблицаКоманд.Добавить();
НоваяКоманда.Представление = "Печать выделенной строки (БСП)";
НоваяКоманда.Идентификатор = "ПечатьВыделеннойСтроки";
// КРИТИЧЕСКИ ВАЖНО: устанавливаем запуск через клиентский метод
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовКлиентскогоМетода();
НоваяКоманда.ПоказыватьОповещение = Ложь;
ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
Возврат ПараметрыРегистрации;
КонецФункции
Функция ПолучитьТаблицуКоманд()
Команды = Новый ТаблицаЗначений;
Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
Возврат Команды;
КонецФункции
Поскольку мы указали тип команды "Вызов клиентского метода", БСП при нажатии кнопки печати будет искать клиентский обработчик в форме внешней печатной формы. Нам необходимо создать основную форму обработки (например, с именем ФормаОбработки) и сделать её формой по умолчанию.
В модуле этой формы мы обязаны прописать экспортную процедуру Печать(). Именно внутри нее мы получим доступ к активному окну документа.
Когда пользователь вызывает печать, управление передается в форму нашей обработки на клиенте. Контекст вызывающей формы (документа Установка цен номенклатуры) будет доступен нам через свойство формы ВладелецФормы. Это ключевой момент всей технологии.
Посмотрим на программную реализацию процедуры Печать() в модуле формы внешней обработки:
&НаКлиенте
Процедура Печать(ИдентификаторКоманды, ОбъектыНазначения) Экспорт
// Проверяем, что форма владельца доступна
Если ВладелецФормы = Неопределено Тогда
ПоказатьПредупреждение(, "Не удалось определить контекст формы документа для получения строк.");
Возврат;
КонецЕсли;
// В зависимости от конфигурации, имя элемента табличной части может отличаться.
// Для документа "Установка цен номенклатуры" в УТ 11 это дерево "ДеревоЦен".
ИмяЭлементаТаблицы = "ДеревоЦен";
Попытка
ТаблицаФормыДокумента = ВладелецФормы.Элементы[ИмяЭлементаТаблицы];
Исключение
ПоказатьПредупреждение(, "Не найден элемент интерфейса '" + ИмяЭлементаТаблицы + "' на форме документа!");
Возврат;
КонецПопытки;
// Сценарий 1: Получаем только ОДНУ текущую активную строку
ТекущиеДанные = ТаблицаФормыДокумента.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
ПоказатьПредупреждение(, "Пожалуйста, выделите строку с номенклатурой в табличной части документа.");
Возврат;
КонецЕсли;
// Извлекаем номенклатуру из активной строки
ВыбраннаяНоменклатура = ТекущиеДанные.Номенклатура;
// Сценарий 2: Если пользователь выделил несколько строк (множественное выделение)
МассивВыделеннойНоменклатуры = Новый Массив;
ВыделенныеСтроки = ТаблицаФормыДокумента.ВыделенныеСтроки;
Для Каждого СтрокаID Из ВыделенныеСтроки Цикл
ДанныеСтроки = ТаблицаФормыДокумента.ДанныеСтроки(СтрокаID);
Если ДанныеСтроки <> Неопределено Тогда
// Проверяем наличие реквизита "Номенклатура" в строке
Если СтруктураСвойства(ДанныеСтроки, "Номенклатура") Тогда
МассивВыделеннойНоменклатуры.Добавить(ДанныеСтроки.Номенклатура);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Если ничего не удалось собрать
Если МассивВыделеннойНоменклатуры.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Не выбрана номенклатура для печати.");
Возврат;
КонецЕсли;
// Передаем собранные данные на сервер для компоновки табличного документа
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("МассивНоменклатуры", МассивВыделеннойНоменклатуры);
ПараметрыПечати.Вставить("ДокументСсылка", ОбъектыНазначения[0]);
ТабличныйДокумент = СформироватьПечатнуюФормуНаСервере(ПараметрыПечати);
// Выводим сформированный документ на экран через стандартные механизмы БСП
КоллекцияПечатныхФорм = УправлениеПечатьюКлиент.НоваяКоллекцияПечатныхФорм(ИдентификаторКоманды);
ПечатнаяФорма = КоллекцияПечатныхФорм[0];
ПечатнаяФорма.ТабличныйДокумент = ТабличныйДокумент;
ПечатнаяФорма.СинонимМакета = "Печать выделенных позиций";
УправлениеПечатьюКлиент.ПечатьДокументов(КоллекцияПечатныхФорм, , ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Функция СтруктураСвойства(Объект, ИмяСвойства)
Свойства = Новый Структура(ИмяСвойства);
ЗаполнитьЗначенияСвойств(Свойства, Объект);
Возврат Свойства[ИмяСвойства] <> Неопределено;
КонецФункции
Теперь, когда мы успешно получили массив ссылок на номенклатуру прямо из интерфейса пользователя на клиенте, мы можем безопасно передать его на сервер для выполнения запроса и вывода макета. Для этого создадим серверную функцию в модуле формы нашей обработки:
&НаСервереБезКонтекста
Функция СформироватьПечатнуюФормуНаСервере(ПараметрыПечати)
ДокументСсылка = ПараметрыПечати.ДокументСсылка;
МассивНоменклатуры = ПараметрыПечати.МассивНоменклатуры;
ТабДок = Новый ТабличныйДокумент;
ТабДок.АвтоМасштаб = Истина;
// Получаем макет из обработки. Поскольку мы находимся в контексте формы ВПФ,
// нам нужно получить объект обработки.
ПечатнаяФормаОбъект = РеквизитФормыВЗначение("Объект");
Макет = ПечатнаяФормаОбъект.ПолучитьМакет("МакетПечати");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
// Выводим шапку документа
ОбластьШапка.Параметры.НомерДокумента = ДокументСсылка.Номер;
ОбластьШапка.Параметры.ДатаДокумента = Формат(ДокументСсылка.Дата, "ДЛФ=D");
ТабДок.Вывести(ОбластьШапка);
// Запрос для получения данных только по ВЫДЕЛЕННЫМ позициям номенклатуры из документа
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| УстановкаЦенНоменклатурыТовары.Номенклатура КАК Номенклатура,
| УстановкаЦенНоменклатурыТовары.Цена КАК Цена,
| УстановкаЦенНоменклатурыТовары.Валюта КАК Валюта
|ИЗ
| Документ.УстановкаЦенНоменклатуры.Товары КАК УстановкаЦенНоменклатурыТовары
|ГДЕ
| УстановкаЦенНоменклатурыТовары.Ссылка = &Ссылка
| И УстановкаЦенНоменклатурыТовары.Номенклатура В(&МассивНоменклатуры)";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
НомерСтроки = 0;
Пока Выборка.Следующий() Цикл
НомерСтроки = НомерСтроки + 1;
ОбластьСтрока.Параметры.Заполнить(Выборка);
ОбластьСтрока.Параметры.НомерСтроки = НомерСтроки;
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
// Выводим подвал
ТабДок.Вывести(ОбластьПодвал);
Возврат ТабДок;
КонецФункции
Рассмотрим ситуацию, когда по техническим причинам вы не хотите или не можете использовать свойство ВладелецФормы (например, при сложной интеграции или вызове команды из сторонней обработки). В этом случае в тонком клиенте можно воспользоваться глобальным методом платформы ПолучитьОкна().
Этот способ позволяет перебрать все открытые окна приложения, найти среди них форму нужного нам документа по ее заголовку или типу объекта и получить доступ к элементам её интерфейса. Проанализируем этот алгоритм:
&НаКлиенте
Процедура ПолучитьСтрокуЧерезОкна()
ОкнаПриложения = ПолучитьОкна();
ФормаДокумента = Неопределено;
Для Каждого ОкноПриложения Из ОкнаПриложения Цикл
// Проверяем содержимое окна
Если ОкноПриложения.Содержимое.Количество() > 0 Тогда
ТекущаяФорма = ОкноПриложения.Содержимое[0];
// Проверяем, является ли форма формой нашего документа
ИмяФормы = ТекущаяФорма.ИмяФормы;
Если Найти(ИмяФормы, "Документ.УстановкаЦенНоменклатуры.Форма.ФормаДокумента") > 0 Тогда
ФормаДокумента = ТекущаяФорма;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если ФормаДокумента <> Неопределено Тогда
// Успешно нашли форму, считываем активные данные
Попытка
ВыделеннаяНоменклатура = ФормаДокумента.Элементы.ДеревоЦен.ТекущиеДанные.Номенклатура;
ПоказатьОповещениеПользователя("Успешно получено", , ВыделеннаяНоменклатура);
Исключение
ЗаписатьЛогОшибки(ОписаниеОшибки());
КонецПопытки;
Иначе
ПоказатьПредупреждение(, "Не удалось найти открытую форму документа 'Установка цен номенклатуры'.");
EndIf;
КонецПроцедуры
Данный метод является более "хрупким", так как зависит от строкового имени формы и заголовка окна, однако он выручает в нестандартных ситуациях, когда разработчик жестко ограничен рамками типового функционала без возможности расширения.
Для того чтобы ваша внешняя печатная форма работала стабильно и не вызывала ошибок при обновлениях конфигурации 1С, рекомендуем придерживаться следующих правил:
Товары, ДеревоЦен, НоменклатурныеПозиции). Всегда оборачивайте обращения к реквизитам формы в блоки Попытка...Исключение.Ctrl или Shift и напечатать их пачкой. Проектируйте печатные формы с расчетом на прием массива позиций, а не одной единственной строки.ПараметрыСеанса.ОбщиеЗначения при активизации строки (как предлагалось в сообщении 13 темы форума) сильно нагружает сервер частыми вызовами и замедляет работу интерфейса пользователя. Используйте передачу контекста только в момент непосредственного нажатия кнопки печати.Мы подробно разобрали, как решить проблему печати выделенной строки номенклатуры в управляемом приложении. Использование клиентского метода БСП и обращение к контексту формы через ВладелецФормы — это надежный, производительный и безопасный способ разработки ВПФ, который гарантирует стабильную работу вашей программы в тонком клиенте.