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

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

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

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

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

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

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


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

// Указываем тип значения для динамического списка
ТабличноеПоле.ТипЗначения = Новый ОписаниеТипов("ДокументСписок.РеализацияТоваровУслуг");

// Устанавливаем геометрическое положение элемента на форме
ТабличноеПоле.Верх = 24;
ТабличноеПоле.Лево = 6;
ТабличноеПоле.Ширина = ЭтаФорма.Ширина - 40;
ТабличноеПоле.Высота = ЭтаФорма.Высота - 50;

// Инициализируем автоматическую генерацию колонок на основе типа значения
ТабличноеПоле.СоздатьКолонки();

Метод СоздатьКолонки() анализирует назначенный ТипЗначения и автоматически генерирует колонки табличного поля, сопоставляя их с реквизитами соответствующего объекта метаданных. Этот подход полностью аналогичен интерактивному перетаскиванию таблицы на форму в конфигураторе.

Шаг 2. Обход ограничения на программное создание реквизитов формы

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

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

Вариант А. Использование экспортируемых переменных модуля формы

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

Реализуем это на примере табличного поля, работающего с произвольной таблицей значений:

  1. В самом верху модуля формы (в разделе объявлений переменных) пишем:

Перем МояТаблицаЭкспорт Экспорт;
  1. В процедуре ПриОткрытии или ПередОткрытием инициализируем таблицу и связываем ее с новым элементом:

Процедура ПриОткрытии()
    // Создаем объект таблицы значений в памяти
    МояТаблицаЭкспорт = Новый ТаблицаЗначений;
    МояТаблицаЭкспорт.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    МояТаблицаЭкспорт.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
    
    // Добавляем элемент управления
    ТабличноеПоле = ЭлементыФормы.Добавить(Тип("ТабличноеПоле"), "ТаблицаТоваров", Истина);
    
    // Связываем свойство Данные со строковым представлением экспортируемой переменной
    ТабличноеПоле.Данные = "МояТаблицаЭкспорт";
    
    // Генерируем колонки
    ТабличноеПоле.СоздатьКолонки();
КонецПроцедуры

Такое табличное поле будет полностью интерактивным. Все изменения, вносимые пользователем в ячейки на форме, будут автоматически записываться в переменную МояТаблицаЭкспорт в режиме реального времени.

Вариант Б. Прямое заполнение свойства «Значение»

Если нам не требуется сложная интерактивная связь «на лету» или мы выводим данные только для чтения, свойству Данные можно ничего не присваивать. Вместо этого мы можем напрямую передать объект (например, заполненную таблицу значений) в свойство Значение элемента управления:


ТабличноеПоле.Значение = МояТаблицаЗначений;
ТабличноеПоле.СоздатьКолонки();

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

Шаг 3. Динамическое подключение к табличной части обработки или документа

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


// ОШИБОЧНЫЙ КОД
Процедура ПриОткрытии()
    ОбработкаОбъект = Обработки.МояОбработка.Создать();
    ЭлементыФормы.Таблица.Данные = "ОбработкаОбъект.ТабличнаяЧастьТовары";
КонецПроцедуры

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

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


// Правильное решение в модуле формы

Перем ОбъектКонтекста Экспорт; // Переменная будет жить, пока открыта форма

Процедура ПриОткрытии()
    // Определяем имя обработки в зависимости от конфигурации
    ИмяОбработки = ?(Метаданные.Имя = "УправлениеТорговлей", "СписокПользователейИБ", "СписокПользователейБД");
    
    // Инициализируем объект в глобальной переменной формы
    ОбъектКонтекста = Обработки[ИмяОбработки].Создать();
    
    // Привязываем данные табличной части к элементу управления
    ЭлементыФормы.ТаблицаПользователей.ТипЗначения = Новый ОписаниеТипов("ОбработкаТабличнаяЧастьСтрока." + ИмяОбработки + ".ТабличнаяЧастьПользователей");
    ЭлементыФормы.ТаблицаПользователей.Данные = "ОбъектКонтекста.ТабличнаяЧастьПользователей";
    ЭлементыФормы.ТаблицаПользователей.СоздатьКолонки();
КонецПроцедуры

Шаг 4. Настройка отборов в программно созданных табличных полях

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

А. Источник данных — Динамический список (ДокументСписок, СправочникСписок)

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

Решение проблемы (Обходной отбор по ссылкам):
Если вам жизненно необходим отбор по сложному или вычисляемому признаку (например, по реквизитам табличных частей документов, которые недоступны в стандартном динамическом списке), выполните предварительный запрос в БД, соберите массив подходящих ссылок и установите отбор по полю Ссылка с видом сравнения ВСписке:


// 1. Получаем массив ссылок с помощью запроса
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	РеализацияТоваровУслуг.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
    |ГДЕ
    |	РеализацияТоваровУслуг.Проект = &ВыбранныйПроект";

Запрос.УстановитьПараметр("ВыбранныйПроект", СсылкаНаПроект);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
МассивСсылок = РезультатЗапроса.ВыгрузитьКолонку("Ссылка");

// 2. Устанавливаем отбор в программном табличном поле списка
ОтборПоСсылке = ЭлементыФормы.НовоеТП.Значение.Отбор.Ссылка;
ОтборПоСсылке.ВидСравнения = ВидСравнения.ВСписке;
ОтборПоСсылке.Значение.Очистить();

Для Каждого ТекущаяСсылка Из МассивСсылок Цикл
    ОтборПоСсылке.Значение.Добавить(ТекущаяСсылка);
КонецЦикла;

ОтборПоСсылке.Использование = Истина;

Б. Источник данных — Таблица значений

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

Решение проблемы:
Для фильтрации таблиц значений на обычных формах используют метод «свопа» данных. В памяти модуля формы хранят полную таблицу-источник (например, в переменной тзИсходная), а в элемент управления Значение подставляют отфильтрованную копию:


// Фильтруем исходную таблицу в памяти
ПараметрыОтбора = Новый Структура("Проект", ВыбранныйПроект);
НайденныеСтроки = тзИсходная.НайтиСтроки(ПараметрыОтбора);

// Переносим отфильтрованные строки в видимое на форме табличное поле
ЭлементыФормы.ТабличноеПоле.Значение = тзИсходная.Скопировать(НайденныеСтроки);

Шаг 5. Оптимизация событий и оформление строк

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

Платформа предлагает два основных события для оформления:

Важное правило оптимизации: Никогда не используйте обращение к базе данных через точку («дерезы») в событии ПриВыводеСтроки. Это приведет к лавинообразному росту SQL-запросов при обычной прокрутке списка пользователем и «замораживанию» интерфейса.

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


// На этапе инициализации табличного поля привязываем событие
ТабличноеПоле.УстановитьДействие("ПриПолученииДанных", Новый Действие("ПриПолученииДанныхНовогоТП"));

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

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

← На главную