При разработке отчетов и обработок на управляемых формах нередко возникает необходимость отобразить пользователю несколько печатных форм. Иногда разработчики сталкиваются с неожиданным поведением платформы: при попытке последовательно вывести отчеты на экран возникает исключение «Недопустимая операция для реквизита формы», даже если переменная табличного документа объявлена локально и не является реквизитом текущей формы.
Разберем детально, почему платформа 1С выдает такую ошибку, как правильно организовать вывод нескольких табличных документов и как программно управлять фокусом открытых окон.
Проанализируем типовой фрагмент кода, приводящий к сбою:
&НаКлиенте
Процедура ПоказатьОтчеты(Команда)
ТабДок = СформироватьДокументНаСервере();
// Первый вызов отрабатывает штатно
ТабДок.Показать("Отчет №1");
// Второй вызов вызывает ошибку: "Недопустимая операция для реквизита формы"
ТабДок.Показать("Отчет №2");
КонецПроцедуры
Причина кроется во внутреннем механизме работы метода Показать():
ТабДок.Показать(), платформа не просто рисует таблицу, а динамически создает системную управляемую форму просмотра.ТабличныйДокумент привязывается к основному реквизиту этой вновь созданной формы.ТабДок.Показать() для того же самого экземпляра платформа предпринимает попытку связать занятый объект со второй системной формой. Так как экземпляр уже прикреплен к реквизиту первой формы, платформа блокирует операцию и генерирует исключение «Недопустимая операция для реквизита формы».Самое прямое и простое решение — гарантировать, что для каждого отдельного окна создается собственный экземпляр типа ТабличныйДокумент. Если на сервере формируется сразу несколько отчетов, возвращайте их через структуру, создавая для каждой печатной формы свой объект через конструктор Новый ТабличныйДокумент.
Посмотрим на пример правильной серверной и клиентской логики:
&НаСервере
Функция СформироватьПакетОтчетовНаСервере()
Результат = Новый Структура;
// Формируем первый документ
ТабДок1 = Новый ТабличныйДокумент;
Макет1 = РеквизитФормыВЗначение("Объект").ПолучитьМакет("МакетПервый");
ОбластьШапка1 = Макет1.ПолучитьОбласть("Шапка");
ТабДок1.Вывести(ОбластьШапка1);
Результат.Вставить("ТабДок1", ТабДок1);
// Обязательно инициализируем НОВЫЙ экземпляр для второго отчета
ТабДок2 = Новый ТабличныйДокумент;
Макет2 = РеквизитФормыВЗначение("Объект").ПолучитьМакет("МакетВторой");
ОбластьШапка2 = Макет2.ПолучитьОбласть("Шапка");
ТабДок2.Вывести(ОбластьШапка2);
Результат.Вставить("ТабДок2", ТабДок2);
Возврат Результат;
КонецФункции
&НаКлиенте
Процедура ВыполнитьПечать(Команда)
Пакет = СформироватьПакетОтчетовНаСервере();
// Теперь каждый документ открывается в своем окне без ошибок
Пакет.ТабДок1.Показать("Первый отчет");
Пакет.ТабДок2.Показать("Второй отчет");
КонецПроцедуры
Если нет жесткого требования открывать несколько независимых вкладок или окон, разберем альтернативный вариант: объединить вывод данных в один объект ТабличныйДокумент. Чтобы при отправке на печать отчеты выводились на разных листах бумаги, используем метод ВывестиГоризонтальныйРазделительСтраниц().
&НаСервере
Функция СформироватьЕдиныйДокумент()
ТабДок = Новый ТабличныйДокумент;
Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("МакетОтчета");
// Формируем первый блок данных
ТабДок.Вывести(Макет.ПолучитьОбласть("Раздел1"));
// Добавляем разделитель страниц для печати
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
// Формируем второй блок данных
ТабДок.Вывести(Макет.ПолучитьОбласть("Раздел2"));
Возврат ТабДок;
КонецФункции
Передача тяжелых объектов ТабличныйДокумент между сервером и клиентом напрямую через возврат из функции приводит к полной сериализации данных в формат XML/XDTO, что создает существенную нагрузку на сеть и оперативную память. Рассмотрим более производительный подход с использованием временного хранилища:
&НаСервере
Функция ПоместитьОтчетыВХранилище()
ТабДок1 = Новый ТабличныйДокумент;
// Заполнение ТабДок1...
ТабДок2 = Новый ТабличныйДокумент;
// Заполнение ТабДок2...
Адрес1 = ПоместитьВоВременноеХранилище(ТабДок1, УникальныйИдентификатор);
Адрес2 = ПоместитьВоВременноеХранилище(ТабДок2, УникальныйИдентификатор);
Результат = Новый Массив;
Результат.Добавить(Адрес1);
Результат.Добавить(Адрес2);
Возврат Результат;
КонецФункции
&НаКлиенте
Процедура ПолучитьИПоказатьОтчеты(Команда)
АдресаХранилища = ПоместитьОтчетыВХранилище();
Для Каждого Адрес Из АдресаХранилища Цикл
ТабДок = ПолучитьИзВременногоХранилища(Адрес);
ТабДок.Показать("Печатная форма");
КонецЦикла;
КонецПроцедуры
Когда мы последовательно открываем несколько табличных документов с помощью Показать(), активным окном (в фокусе пользователя) всегда остается то, которое было вызвано последним. Нередко требуется оставить активным самое первое окно.
Метод Показать() возвращает значение Неопределено, поэтому получить прямую ссылку на созданную форму через результат функции нельзя. Однако мы можем управлять окнами через глобальную коллекцию клиентского приложения.
Разберем по шагам, как активизировать первое открытое окно по его заголовку с помощью метода ПолучитьОкна():
&НаКлиенте
Процедура ОткрытьИАктивизировать(Команда)
Пакет = СформироватьПакетОтчетовНаСервере();
ЗаголовокПервого = "УСНО: 9мес2026 (Основной)";
ЗаголовокВторого = "Расшифровка затрат";
// Открываем документы последовательно
Пакет.ТабДок1.Показать(ЗаголовокПервого);
Пакет.ТабДок2.Показать(ЗаголовокВторого);
// Находим первое окно среди открытых окон приложения и активизируем его
МассивОкон = ПолучитьОкна();
Для Каждого ОкноПриложения Из МассивОкон Цикл
Если ОкноПриложения.Заголовок = ЗаголовокПервого Тогда
ОкноПриложения.Активизировать();
Прервать;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Метод ПолучитьОкна() возвращает массив объектов типа ОкноКлиентскогоПриложения. Сравнивая свойство Заголовок каждого окна со строкой, переданной в Показать(), мы находим требуемое окно и вызываем для него Активизировать(), возвращая фокус интерфейса на начальный отчет.