Как правильно добавить произвольные редактируемые колонки в табличное поле?

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

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

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

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

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

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

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

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

Способ 1. Использование несвязанных колонок и внешнего хранилища данных

Разберем вариант, когда табличная часть документа остается привязанной к табличному полю, а дополнительные колонки создаются динамически. Чтобы данные не терялись, строки табличной части должны иметь стабильный ключ (например, реквизит ИдентификаторСтроки или уникальный ключ), а значения ячеек должны храниться во внешней коллекции — например, в переменной формы типа ТаблицаЗначений или Соответствие.

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

  1. Создаем структуру для хранения данных в форме (например, в таблице тзДопАтрибуты, где есть колонка Ключ и динамические колонки).
  2. Добавляем колонки в само табличное поле, задаем элемент управления и назначаем обработчик изменения.

Посмотрим на пример инициализации колонок:


// Добавляем колонку в таблицу-хранилище
тзДопАтрибуты.Колонки.Добавить(стИдентификатор, текОписаниеТипа, стрНаименование);

// Добавляем колонку в табличное поле формы
ОбъектКолонка = ТабличноеПолеНаФорме.Колонки.Добавить(стИдентификатор, стрНаименование);
ОбъектКолонка.УстановитьЭлементУправления(Тип("ПолеВвода"));
ОбъектКолонка.ЭлементУправления.ТипЗначения = текОписаниеТипа;

// Назначаем обработчик изменения значения
ОбъектКолонка.ЭлементУправления.УстановитьДействие("ПриИзменении", Новый Действие("КолонкаДопАтрибута_ПриИзменении"));

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


Процедура ТабличноеПолеНаФорме_ПриПолученииДанных(Элемент, ОформленияСтрок)
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        НайденнаяСтрока = тзДопАтрибуты.Найти(ОформлениеСтроки.ДанныеСтроки.ИдентификаторСтроки, "Ключ");
        Если НайденнаяСтрока = Неопределено Тогда 
            Продолжить; 
        КонецЕсли;
        
        Для Каждого Колонка Из тзДопАтрибуты.Колонки Цикл
            Если Колонка.Имя = "Ключ" Тогда 
                Продолжить; 
            КонецЕсли;
            
            // Задаем отображаемое значение и текст ячейки
            ЗначениеИзБуфера = НайденнаяСтрока[Колонка.Имя];
            ОформлениеСтроки.Ячейки[Колонка.Имя].Значение = ЗначениеИзБуфера;
            ОформлениеСтроки.Ячейки[Колонка.Имя].Текст = Строка(ЗначениеИзБуфера);
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

Теперь разберем критически важный момент — передачу значения в редактор при активации ячейки. Чтобы редактор не оставался пустым или не содержал данных предыдущей строки, обрабатываем событие ПриНачалеРедактирования табличного поля:


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

Сохранение отредактированного значения обратно в буфер выполняется в обработчике события ПриИзменении самого элемента управления:


Процедура КолонкаДопАтрибута_ПриИзменении(Элемент)
    ТекущиеДанные = ЭлементыФормы.ТабличноеПолеНаФорме.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    ИмяКолонки = ЭлементыФормы.ТабличноеПолеНаФорме.ТекущаяКолонка.Имя;
    НайденнаяСтрока = тзДопАтрибуты.Найти(ТекущиеДанные.ИдентификаторСтроки, "Ключ");
    
    Если НайденнаяСтрока <> Неопределено Тогда
        НайденнаяСтрока[ИмяКолонки] = Элемент.Значение;
        // Принудительно обновляем строки для перерисовки
        ЭлементыФормы.ТабличноеПолеНаФорме.ОбновитьСтроки();
    КонецЕсли;
КонецПроцедуры

Способ 2. Виртуальная таблица значений (рекомендуемый подход)

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

В этом случае разделяют уровень хранения данных и уровень интерфейса:

  1. В объекте данные хранятся в классических нормализованных табличных частях (например, строки с полями Номенклатура, Склад, Количество).
  2. На форме создается реквизит типа ТаблицаЗначений, который назначается источником данных для табличного поля (свойство Данные = "ТЗФормы").
  3. При открытии формы табличная часть с помощью запроса разворачивается в кросс-таблицу. В ТЗФормы динамически создаются нужные колонки соответствующих типов.
  4. Вызывается метод табличного поля ЭлементыФормы.ТабличноеПоле.СоздатьКолонки().

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

При записи документа достаточно выполнить обратную операцию: обойти строки ТЗФормы, собрать значения из колонок складов и перезаполнить физическую табличную часть документа.

Важные рекомендации при реализации

  1. Уникальный идентификатор строки: никогда не используйте НомерСтроки в качестве ключа связи с внешней коллекцией. При любой интерактивной сортировке, удалении или перемещении строк номера изменятся, что приведет к рассинхронизации введенных данных. Используйте стабильный идентификатор (например, реквизит с типом УникальныйИдентификатор или числовой счетчик).
  2. Производительность: обработчик ПриПолученииДанных вызывается регулярно при любой перерисовке. Если вы используете таблицу значений для буфера, обязательно создайте индекс по ключевой колонке с помощью метода Индексы.Добавить("Ключ"), либо используйте объект Соответствие, чтобы минимизировать задержки интерфейса.
← На главную