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

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

При разработке интерфейсов на обычных формах в среде 1С:Предприятие 8 программисты часто сталкиваются с задачей вывода дополнительных, вычисляемых или динамических данных в табличные поля. Типичный пример: на форме документа или справочника необходимо показать несколько колонок с дополнительными свойствами номенклатуры (например, артикулы различных поставщиков), количество которых заранее может быть неизвестно или ограничено разумным пределом (например, до 5 колонок).

Попытки напрямую записать значения в элементы управления или колонки табличной части в цикле (вроде ЭлементыФормы.ТабличнаяЧасть.Колонки.ИмяКолонки = Значение) неизбежно приводят к ошибкам. В этой статье мы подробно разберем, почему стандартные подходы не работают, проанализируем критические ошибки начинающих разработчиков и рассмотрим два правильных способа решения этой задачи: простой — через событие ПриВыводеСтроки, и профессиональный (высокопроизводительный) — через событие ПриПолученииДанных.

Почему прямая запись в колонки не работает

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

Однако, если мы создаем «виртуальные» колонки (например, АртикулПоставщика1, АртикулПоставщика2 и т.д.), которые не привязаны к физическим реквизитам табличной части, то напрямую присвоить им значение в объекте невозможно. Попытка написать код:

ЭлементыФормы.Товары.Колонки["АртикулПоставщика" + Индекс] = НужныйАртикул;

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

Разбор критических ошибок в коде

Рассмотрим код, который часто пытаются использовать начинающие разработчики (именно такой пример обсуждался на форуме):


Для каждого Строка ИЗ Товары Цикл
    Для каждого СтрокаПоставщики ИЗ Строка.Номенклатура.МТД_Поставщики Цикл
        ЭлементыФормы.Товары.Колонки["АртикулПоставщика"+СтрокаПоставщики.НомерСтроки].Видимость = Истина;
        ОформлениеСтроки.Ячейки["АртикулПоставщика"+СтрокаПоставщики.НомерСтроки].Значение = СтрокаПоставщики.Артикул;
        Если СтрокаПоставщики.НомерСтроки = 5 Тогда
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

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

  1. Лишний цикл по всей табличной части (Для каждого Строка ИЗ Товары): События отрисовки строк (такие как ПриВыводеСтроки) вызываются платформой 1С для каждой строки отдельно в момент ее появления на экране. Переданный в параметры обработчика объект ОформлениеСтроки содержит оформление только одной конкретной строки, которая рисуется в данный момент. Запуская внутри этого события цикл по всем строкам документа, мы заставляем систему выполнять колоссальный объем лишней работы, а ячейки текущей строки постоянно перезаписываются данными последней строки из таблицы. Именно поэтому «весь столбик» заполняется одинаковыми, неверными значениями.
  2. Изменение видимости колонок в процессе отрисовки: Метод ЭлементыФормы.Товары.Колонки[...].Видимость = Истина меняет свойства структуры самой формы. Любое изменение видимости колонки заставляет таблицу полностью перерисовываться, что снова вызывает событие отрисовки строк. Это порождает бесконечную рекурсию или, как минимум, катастрофическое падение производительности интерфейса при любой попытке прокрутить список. Управлять видимостью колонок нужно один раз — при открытии формы или при изменении ключевых реквизитов, но никогда внутри событий вывода строк.
  3. Присвоение значения свойству Значение: Для несвязанных текстовых колонок правильнее использовать метод УстановитьТекст() объекта ЯчейкаТабличногоПоля, так как свойство Значение предназначено для типизированных данных, связанных с источником формы.

Решение №1: Использование события ПриВыводеСтроки (Простой вариант)

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

Проанализируем правильный вариант реализации:


Процедура ТоварыПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
    
    // Проверяем, что в строке есть данные (это не группировка и не пустая строка)
    Если ДанныеСтроки = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Работаем только с данными текущей строки!
    СсылкаНаНоменклатуру = ДанныеСтроки.Номенклатура;
    
    Если ЗначениеЗаполнено(СсылкаНаНоменклатуру) Тогда
        
        // Перебираем подчиненную табличную часть (или регистр сведений) с артикулами
        Для каждого СтрокаПоставщика Из СсылкаНаНоменклатуру.МТД_Поставщики Цикл
            
            // Ограничиваемся первыми 5 колонками, как спроектировано на форме
            Если СтрокаПоставщика.НомерСтроки > 5 Тогда
                Прервать;
            КонецЕсли;
            
            ИмяЯчейки = "АртикулПоставщика" + СтрокаПоставщика.НомерСтроки;
            
            // Находим нужную ячейку в текущей оформляемой строке
            Ячейка = ОформлениеСтроки.Ячейки.Найти(ИмяЯчейки);
            Если Ячейка <> Неопределено Тогда
                // Устанавливаем текст для визуального отображения
                Ячейка.УстановитьТекст(СтрокаПоставщика.Артикул);
            КонецЕсли;
            
        КонецЦикла;
        
    КонецЕсли;
    
КонецПроцедуры

Этот код работает корректно и решает проблему перезаписи всей колонки одинаковыми значениями. Каждая строка теперь обрабатывается изолированно, выводя на экран именно те артикулы, которые привязаны к номенклатуре в текущей строке табличной части.

Решение №2: Использование ПриПолученииДанных (Оптимальный и быстрый вариант)

Несмотря на то, что первое решение выглядит простым и понятным, у него есть огромный скрытый недостаток — проблема запросов N+1. Обращаясь в коде через точку к подчиненному объекту (ДанныеСтроки.Номенклатура.МТД_Поставщики), мы заставляем платформу делать отдельный неявный запрос к базе данных для каждой отрисовываемой строки.

Если пользователь откроет документ, в котором содержится 100 строк номенклатуры, система выполнит 100 отдельных микрозапросов к СУБД при выводе таблицы на экран. Это приведет к сильным зависаниям («лагам») при прокрутке списка мышкой.

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

Давайте рассмотрим этот профессиональный метод детально:


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

Проанализировав работу этого алгоритма, мы увидим, что скорость вывода строк увеличилась в десятки раз. Вместо множества мелких обращений к СУБД происходит ровно одно обращение, а распределение значений по ячейкам в оперативной памяти выполняется мгновенно.

Важные ограничения динамического оформления ячеек

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

Это означает следующее:

Что делать, если по этим колонкам нужны поиск и сортировка?

Если бизнес-требования обязывают выполнять фильтрацию, поиск или сортировку по артикулам поставщиков внутри табличной части документа, вам придется отказаться от динамического вывода через оформление строк. Единственным верным решением в данном случае будет создание реальных реквизитов в табличной части документа (например, реквизиты АртикулПоставщика1 ... АртикулПоставщика5 с типом Строка). Заполнять их физически значениями нужно будет в событии ПриИзменении колонки Номенклатура или при заполнении/пересчете всего документа.

← На главную