Как правильно вывести данные запроса в табличный документ на управляемой форме по макету в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработка отчетов и печатных форм в среде 1С:Предприятие 8.3 на управляемых формах часто вызывает трудности у начинающих программистов. Одной из наиболее типичных проблем является путаница между экранными элементами и физическими таблицами базы данных, а также непонимание специфики передачи данных между клиентом и сервером. Выясним причину популярных ошибок и разберем по шагам, как правильно реализовать вывод данных запроса по макету непосредственно в форму.

Разница между табличной частью и табличным документом

Прежде чем перейти к написанию кода, проанализируем терминологию, в которой часто путаются новички. В заголовках форумов нередко звучит фраза «вывод запроса в табличную часть по макету». Однако это принципиальное архитектурное заблуждение.

Табличная часть (ТЧ) — это жестко структурированный объект конфигурации (или реквизит формы типа ДанныеФормыКоллекция), состоящий из строго определенного набора колонок. Она предназначена для хранения табличных данных (например, перечня товаров в документе). В табличную часть нельзя произвольным образом вывести графический макет, объединить ячейки крест-накрест, раскрасить отдельные строки в разные цвета программным путем или установить произвольные шрифты для отдельных областей.

Табличный документ (ТабДок) — это специализированный объект платформы 1С (тип ТабличныйДокумент), который представляет собой аналог электронных таблиц Excel. Именно он используется для формирования отчетов, прайс-листов и печатных форм. Табличный документ состоит из ячеек, поддерживает группировки, вывод областей макета, сложные форматирования и условное оформление. На управляемой форме он отображается через элемент управления Поле табличного документа.

Поэтому, если перед нами стоит задача вывести структурированный отчет с шапкой, группировками контрагентов и подвалами по созданному макету, мы всегда работаем с Табличным документом, а не с табличной частью.

Причина ошибки «Нельзя изменять поле, содержащее данные объекта» на клиенте

Рассмотрим ситуацию, с которой столкнулся автор темы при попытке присвоить значение на клиенте:


&НаКлиенте
Процедура Сформировать(Команда)
    Табл = СформироватьНаСервере();
    Отчет.ТабДок = Табл; // ОШИБКА! Нельзя изменять поле, содержащее данные объекта
КонецПроцедуры

Выясним причину этого поведения платформы. В управляемом приложении 1С разделение на клиент и сервер реализовано максимально строго. На стороне клиента реквизиты формы, связанные с данными объекта (в данном случае Отчет.ТабДок), представлены специальными прокси-объектами (оболочками), которые обеспечивают интерактивную связь с интерфейсом пользователя.

Платформа 1С запрещает прямую перезапись (замену ссылки) таких сложных прикладных объектов на клиенте. Когда мы пытаемся вернуть с сервера новый физический объект типа ТабличныйДокумент и присвоить его свойству Отчет.ТабДок, система генерирует исключение, так как эта операция нарушает синхронизацию данных формы.

Решить эту проблему можно двумя путями:

  1. Использовать контекстный серверный вызов (процедура &НаСервере), где реквизит формы доступен напрямую и платформа сама синхронизирует изменения с клиентом.
  2. Если используется внеконтекстный вызов &НаСервереБезКонтекста, то возвращенный табличный документ нужно не переприсваивать целиком, а копировать его содержимое с помощью метода Вывести() или использовать другие механизмы загрузки данных на сервере.

Ошибка при переносе методов обычных форм в управляемые

В процессе обсуждения на форумах часто всплывают устаревшие советы времен платформы 1С 8.1 или обычного интерфейса 8.2, например:

ЭлементыФормы.ПолеТабличногоДокумента1.Вывести(ТабДок);

Попытка использовать этот код в управляемом приложении приведет к ошибке компиляции по двум причинам:

Правильная архитектура работы с табличным документом

Рассмотрим наиболее простой, надежный и производительный способ формирования отчета по макету на управляемой форме. Для этого мы будем использовать контекстный вызов сервера. В этом случае нам не нужно передавать тяжелые табличные документы между клиентом и сервером вручную — платформа сделает это автоматически в рамках стандартного вызова.

Предположим, у нас есть отчет. В реквизитах отчета (или самой формы) создан реквизит ТабДок с типом ТабличныйДокумент. На форме размещен элемент управления типа Поле табличного документа, путь к данным которого указывает на наш реквизит ТабДок.

Разберем по шагам правильный алгоритм формирования:


&НаКлиенте
Процедура Сформировать(Команда)
    
    // Вызываем контекстную процедуру на сервере
    СформироватьНаСервере();
    
КонецПроцедуры

&НаСервере
Процедура СформироватьНаСервере()
    
    // Шаг 1. Очищаем наш реквизит формы ТабДок перед новым выводом
    ТабДок.Очистить();
    
    // Шаг 2. Получаем объект отчета, чтобы добраться до его макетов
    ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
    
    // Шаг 3. Получаем макет по имени
    Макет = ОтчетОбъект.ПолучитьМакет("МакетОтчета");
    
    // Шаг 4. Получаем области макета для последующего заполнения
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    ОбластьДанныеКонтрагент = Макет.ПолучитьОбласть("СтрДанныеКонтрагент");
    ОбластьДанныеДоговор = Макет.ПолучитьОбласть("СтрДанныеДоговорКонтрагента");
    ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
    
    // Шаг 5. Заполняем параметры шапки из реквизитов отчета (или элементов формы)
    // Обратите внимание: обращаться к реквизитам отчета на сервере нужно через "Отчет"
    ОбластьШапка.Параметры.НачалоПериода = Отчет.НачалоПериода;
    ОбластьШапка.Параметры.КонецПериода  = Отчет.КонецПериода;
    
    // Выводим шапку в наш ТабличныйДокумент формы
    ТабДок.Вывести(ОбластьШапка);
    
    // Шаг 6. Создаем и настраиваем запрос для получения данных
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("Счет62", ПланыСчетов.Хозрасчетный.НайтиПоКоду("62"));
    Запрос.УстановитьПараметр("НачалоПериода", Отчет.НачалоПериода);
    Запрос.УстановитьПараметр("КонецПериода", Отчет.КонецПериода);
    
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ХозрасчетныйОбороты.Субконто1 КАК Контрагент,
    |	ХозрасчетныйОбороты.Субконто2 КАК ДоговорКонтрагента,
    |	ХозрасчетныйОбороты.СуммаОборотДт КАК Приход,
    |	ХозрасчетныйОбороты.СуммаОборотКт КАК Расход
    |ИЗ
    |	РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет В ИЕРАРХИИ (&Счет62), , , , ) КАК ХозрасчетныйОбороты
    |ИТОГИ
    |	СУММА(Приход),
    |	СУММА(Расход)
    |ПО
    |	Контрагент,
    |	ДоговорКонтрагента";
    
    РезультатЗапроса = Запрос.Выполнить();
    
    // Шаг 7. Обход результата запроса по группировкам для построения иерархии
    ВыборкаКонтрагент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    
    ИтогоПриход = 0;
    ИтогоРасход = 0;
    
    // Включаем автогруппировку строк для красивого сворачивания по "+"
    ТабДок.НачатьАвтогруппировкуСтрок();
    
    Пока ВыборкаКонтрагент.Следующий() Цикл
        
        // Очищаем параметры перед заполнением
        ОбластьДанныеКонтрагент.Параметры.Заполнить(Новый Структура("Приход, Расход", 0, 0));
        
        // Заполняем параметры области данными из выборки контрагента
        ЗаполнитьЗначенияСвойств(ОбластьДанныеКонтрагент.Параметры, ВыборкаКонтрагент);
        
        // Выводим строку контрагента (уровень группировки 1)
        ТабДок.Вывести(ОбластьДанныеКонтрагент, 1);
        
        // Выбираем подчиненные договоры для текущего контрагента
        ВыборкаДоговор = ВыборкаКонтрагент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
        
        Пока ВыборкаДоговор.Следующий() Цикл
            
            ОбластьДанныеДоговор.Параметры.Заполнить(Новый Структура("Приход, Расход", 0, 0));
            ЗаполнитьЗначенияСвойств(ОбластьДанныеДоговор.Параметры, ВыборкаДоговор);
            
            // Выводим строку договора (уровень группировки 2)
            ТабДок.Вывести(ОбластьДанныеДоговор, 2);
            
            // Накапливаем общие итоги
            ИтогоПриход = ИтогоПриход + ВыборкаДоговор.Приход;
            ИтогоРасход = ИтогоРасход + ВыборкаДоговор.Расход;
            
        КонецЦикла;
        
    КонецЦикла;
    
    // Завершаем группировку строк
    ТабДок.ЗакончитьАвтогруппировкуСтрок();
    
    // Шаг 8. Вывод подвала с итогами
    ОбластьПодвал.Параметры.ИтогоПриход = ИтогоПриход;
    ОбластьПодвал.Параметры.ИтогоРасход = ИтогоРасход;
    ТабДок.Вывести(ОбластьПодвал);
    
КонецПроцедуры

Проанализируем ключевые моменты представленного кода:

Автоматизация вывода через Систему компоновки данных (СКД)

В заголовке исходной темы упоминается аббревиатура СКД. Если для построения отчета вы используете Систему компоновки данных, то ручной обход выборок, создание запросов в коде и верстка макетов ячейка за ячейкой становятся избыточными операциями. СКД умеет самостоятельно генерировать структуру отчета по заданным настройкам.

Рассмотрим, как красиво и лаконично вывести отчет СКД в табличный документ на управляемой форме программным путем:


&НаСервере
Процедура СформироватьОтчетСКДНаСервере()
    
    // 1. Очищаем результирующий табличный документ на форме
    ТабДок.Очистить();
    
    // 2. Получаем схему компоновки данных из макетов отчета
    ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
    СхемаКомпоновкиДанных = ОтчетОбъект.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    
    // 3. Получаем текущие настройки пользователя из компоновщика настроек
    Настройки = Отчет.КомпоновщикНастроек.ПолучитьНастройки();
    
    // 4. Выполняем компоновку макета с учетом настроек пользователя
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки);
    
    // 5. Инициализируем процессор компоновки данных полученным макетом
    ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки);
    
    // 6. Выводим результат в табличный документ формы через процессор вывода
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ТабДок);
    ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
    
КонецПроцедуры

Использование СКД предоставляет разработчику колоссальные преимущества перед ручным выводом макетов:

  1. Минимум кода: Платформа сама заботится о выводе иерархии, оформлении строк, расчете итогов по группировкам и общих итогов по отчету.
  2. Гибкость для пользователя: Пользователь может прямо на форме в режиме «1С:Предприятие» добавлять новые группировки, отборы, менять сортировку и оформление отчета без изменения программного кода.
  3. Условное оформление: Настройка подсветки отрицательных чисел, выделение важных контрагентов цветом или изменение шрифтов настраивается в СКД за пару кликов мыши в конфигураторе или режиме пользователя.

Таким образом, для простых печатных форм документов (например, Торг-12 или счет на оплату), где структура жестко фиксирована стандартами, идеально подходит классический вывод по макету с ручным обходом запроса. Для построения многофункциональных аналитических отчетов (обороты по счетам, продажи по периодам, остатки товаров) золотым стандартом является использование механизмов СКД.

← На главную