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

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

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

Разберем по шагам весь процесс: от правильной инициализации динамических реквизитов до алгоритмов поддержания актуальности данных в строках таблицы.

Шаг 1. Создание динамического реквизита и элемента управления на форме

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

Рассмотрим пример создания числового реквизита Остаток в существующей табличной части Товары основного реквизита Объект:


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

Обратите внимание на последний параметр конструктора РеквизитФормы (свойство СохраняемыеДанные). Если передать значение Ложь, изменение этого поля не будет взводить признак модифицированности формы (так называемую «звездочку» в заголовке окна), что предотвратит появление ложных вопросов пользователю о необходимости сохранения данных.

Шаг 2. Пакетное заполнение значений колонок при открытии формы

Выясним причину частой ошибки начинающих разработчиков: получение данных в цикле отдельным запросом по каждой строке. Такой подход критически снижает производительность формы при большом количестве позиций в документе. Заполнение необходимо выполнять пакетно за один запрос к базе данных.

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


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

Шаг 3. Сохранение и пересчет данных при записи и перечитывании

Поскольку добавленные через ИзменитьРеквизиты данные не сохраняются в физической таблице базы данных, при стандартной записи объекта или перечитывании формы платформа полностью переинициализирует коллекцию Объект.Товары, сбрасывая все программно заполненные значения в значения по умолчанию (для чисел — 0).

Чтобы данные не исчезали после нажатия кнопки «Записать», необходимо повторно вызывать процедуру заполнения в серверном обработчике ПослеЗаписиНаСервере:


&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
    
    ЗаполнитьОстаткиВТаблицеНаСервере();
    
КонецПроцедуры

Шаг 4. Поддержание актуальности при добавлении и редактировании строк

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

Посмотрим на пример оптимальной реализации с использованием контекстно-независимого серверного вызова:


&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем остаток по выбранной позиции на сервере без контекста формы
    ТекущиеДанные.Остаток = ПолучитьОстатокНоменклатуры(ТекущиеДанные.Номенклатура, Объект.Склад);
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьОстатокНоменклатуры(Номенклатура, Склад)
    
    Если Не ЗначениеЗаполнено(Номенклатура) Или Не ЗначениеЗаполнено(Склад) Тогда
        Возврат 0;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
        |ИЗ
        |	РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура И Склад = &Склад) КАК ТоварыНаСкладахОстатки";
        
    Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Остаток;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Рекомендации и альтернативные подходы

  1. Использование расширений конфигурации: Если конфигурация находится на типовой поддержке, значительно удобнее заимствовать форму в расширение и добавить реквизит формы и элемент визуально через конфигуратор. Это избавит код от рутинного вызова ИзменитьРеквизиты и исключит возможные ошибки при назначении путей к данным.
  2. Избегайте повторного вызова ИзменитьРеквизиты: Никогда не вызывайте метод перестроения реквизитов внутри клиентских событий (например, при нажатии кнопок или переключении страниц), так как это приводит к полной очистке временных несохраненных данных пользователя на форме.
← На главную