При разработке интерфейсов в обычном приложении 1С часто возникает задача добавить в табличное поле дополнительные колонки, которых изначально нет в объекте конфигурации или табличной части. Типичный пример — отображение остатков или потребностей в разрезе динамического списка складов («шахматка» или кросс-таблица). Однако при программном добавлении колонок без заполненного свойства Данные разработчики сталкиваются с тем, что введенные значения либо сразу исчезают при потере фокуса, либо дублируются по всем строкам при активации ячейки.
Выясним причину такого поведения платформы и подробно разберем правильные подходы к реализации этой задачи.
Проанализируем механику работы табличного поля в обычном приложении. Для каждой колонки создается единственный экземпляр элемента управления (например, поле ввода), который физически перемещается по строкам при клике пользователя, а не по отдельному элементу на каждую ячейку таблицы.
Когда колонка не связана со структурой строки через свойство Данные, возникают следующие проблемы:
Для корректной работы с такими колонками необходимо либо связать отображение с внешним буфером, либо использовать промежуточную таблицу значений.
Разберем вариант, когда табличная часть документа остается привязанной к табличному полю, а дополнительные колонки создаются динамически. Чтобы данные не терялись, строки табличной части должны иметь стабильный ключ (например, реквизит ИдентификаторСтроки или уникальный ключ), а значения ячеек должны храниться во внешней коллекции — например, в переменной формы типа ТаблицаЗначений или Соответствие.
Рассмотрим по шагам программное создание колонок и привязку событий:
тзДопАтрибуты, где есть колонка Ключ и динамические колонки).Посмотрим на пример инициализации колонок:
// Добавляем колонку в таблицу-хранилище
тзДопАтрибуты.Колонки.Добавить(стИдентификатор, текОписаниеТипа, стрНаименование);
// Добавляем колонку в табличное поле формы
ОбъектКолонка = ТабличноеПолеНаФорме.Колонки.Добавить(стИдентификатор, стрНаименование);
ОбъектКолонка.УстановитьЭлементУправления(Тип("ПолеВвода"));
ОбъектКолонка.ЭлементУправления.ТипЗначения = текОписаниеТипа;
// Назначаем обработчик изменения значения
ОбъектКолонка.ЭлементУправления.УстановитьДействие("ПриИзменении", Новый Действие("КолонкаДопАтрибута_ПриИзменении"));
Для корректного отображения значений на экране необходимо задействовать событие табличного поля ПриПолученииДанных. В этом событии мы сопоставляем строки и заполняем свойства ячеек:
Процедура ТабличноеПолеНаФорме_ПриПолученииДанных(Элемент, ОформленияСтрок)
Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
НайденнаяСтрока = тзДопАтрибуты.Найти(ОформлениеСтроки.ДанныеСтроки.ИдентификаторСтроки, "Ключ");
Если НайденнаяСтрока = Неопределено Тогда
Продолжить;
КонецЕсли;
Для Каждого Колонка Из тзДопАтрибуты.Колонки Цикл
Если Колонка.Имя = "Ключ" Тогда
Продолжить;
КонецЕсли;
// Задаем отображаемое значение и текст ячейки
ЗначениеИзБуфера = НайденнаяСтрока[Колонка.Имя];
ОформлениеСтроки.Ячейки[Колонка.Имя].Значение = ЗначениеИзБуфера;
ОформлениеСтроки.Ячейки[Колонка.Имя].Текст = Строка(ЗначениеИзБуфера);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Теперь разберем критически важный момент — передачу значения в редактор при активации ячейки. Чтобы редактор не оставался пустым или не содержал данных предыдущей строки, обрабатываем событие ПриНачалеРедактирования табличного поля:
Процедура ТабличноеПолеНаФорме_ПриНачалеРедактирования(Элемент, Кнопка, Отказ)
ИмяКолонки = Элемент.ТекущаяКолонка.Имя;
Если тзДопАтрибуты.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
Возврат;
КонецЕсли;
// Получаем текущую строку и извлекаем значение из буфера
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
НайденнаяСтрока = тзДопАтрибуты.Найти(ТекущиеДанные.ИдентификаторСтроки, "Ключ");
Если НайденнаяСтрока <> Неопределено Тогда
Элемент.Колонки[ИмяКолонки].ЭлементУправления.Значение = НайденнаяСтрока[ИмяКолонки];
КонецЕсли;
КонецПроцедуры
Сохранение отредактированного значения обратно в буфер выполняется в обработчике события ПриИзменении самого элемента управления:
Процедура КолонкаДопАтрибута_ПриИзменении(Элемент)
ТекущиеДанные = ЭлементыФормы.ТабличноеПолеНаФорме.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
ИмяКолонки = ЭлементыФормы.ТабличноеПолеНаФорме.ТекущаяКолонка.Имя;
НайденнаяСтрока = тзДопАтрибуты.Найти(ТекущиеДанные.ИдентификаторСтроки, "Ключ");
Если НайденнаяСтрока <> Неопределено Тогда
НайденнаяСтрока[ИмяКолонки] = Элемент.Значение;
// Принудительно обновляем строки для перерисовки
ЭлементыФормы.ТабличноеПолеНаФорме.ОбновитьСтроки();
КонецЕсли;
КонецПроцедуры
Рассмотрим альтернативное и более надежное архитектурное решение. Если колонок много или они отражают сложную аналитику (например, склады с остатками), ручная синхронизация через обработчики событий становится трудоемкой и может снижать скорость отклика интерфейса при быстром скролле.
В этом случае разделяют уровень хранения данных и уровень интерфейса:
Номенклатура, Склад, Количество).ТаблицаЗначений, который назначается источником данных для табличного поля (свойство Данные = "ТЗФормы").ТЗФормы динамически создаются нужные колонки соответствующих типов.ЭлементыФормы.ТабличноеПоле.СоздатьКолонки().Преимущество этого способа: свойство Данные у каждой созданной колонки заполнено реальным именем колонки таблицы значений. Платформа берет на себя все заботы по открытию редакторов, валидации типов, отображению и сохранению данных в строке без необходимости писать код в ПриПолученииДанных и ПриНачалеРедактирования.
При записи документа достаточно выполнить обратную операцию: обойти строки ТЗФормы, собрать значения из колонок складов и перезаполнить физическую табличную часть документа.
НомерСтроки в качестве ключа связи с внешней коллекцией. При любой интерактивной сортировке, удалении или перемещении строк номера изменятся, что приведет к рассинхронизации введенных данных. Используйте стабильный идентификатор (например, реквизит с типом УникальныйИдентификатор или числовой счетчик).ПриПолученииДанных вызывается регулярно при любой перерисовке. Если вы используете таблицу значений для буфера, обязательно создайте индекс по ключевой колонке с помощью метода Индексы.Добавить("Ключ"), либо используйте объект Соответствие, чтобы минимизировать задержки интерфейса.