Как использовать таблицу значений в качестве источника данных для динамического списка в 1С

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

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

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

Почему динамический список не работает с таблицей значений напрямую?

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

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

  1. Проблема сериализации данных (XDTO): Динамический список выполняется на сервере в собственном изолированном контексте. Платформа 1С не умеет передавать объект типа ТаблицаЗначений через границу этого контекста. Попытка передать ТЗ как параметр запроса приводит к ошибке сериализации XDTO.
  2. Отсутствие ключевых полей и основной таблицы: Для корректной прокрутки, позиционирования и выбора строк динамическому списку необходим уникальный ключ (например, ссылка на элемент справочника или уникальный ключ записи регистра). В произвольной таблице значений таких гарантированных уникальных идентификаторов по умолчанию нет.
  3. Ограничения временных таблиц: Начиная с версии платформы 8.3.8, в ДС разрешено использовать пакетные запросы. Тем не менее, создать временную таблицу на основе переданной таблицы значений внутри динамического списка невозможно, так как источник данных должен физически находиться в СУБД.

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

---

Решение 1. Использование буферного регистра сведений в базе данных

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

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

Разберем по шагам схему реализации:

  1. Создадим вспомогательный регистр сведений (например, БуферВременныхДанных).
  2. Первым ключевым измерением укажем ИдентификаторСессии с типом УникальныйИдентификатор. Это позволит изолировать данные разных пользователей, работающих одновременно.
  3. Остальные измерения и ресурсы регистра должны в точности соответствовать колонкам вашей исходной таблицы значений.
  4. На форме настраиваем динамический список, где в качестве основной таблицы указываем наш регистр сведений.

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


// Генерируем уникальный ключ для текущего сеанса работы с формой
ИдентификаторСессии = Новый УникальныйИдентификатор;

// Записываем данные ТЗ в буферный регистр
НаборЗаписей = РегистрыСведений.БуферВременныхДанных.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ИдентификаторСессии.Установить(ИдентификаторСессии);

Для Каждого СтрокаТЗ Из МояИсходнаяТаблица Цикл
    НоваяЗапись = НаборЗаписей.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаТЗ);
    НоваяЗапись.ИдентификаторСессии = ИдентификаторСессии;
КонецЦикла;

НаборЗаписей.Записать();

Теперь передаем этот идентификатор в параметры динамического списка, чтобы пользователь видел только свои данные:


// Устанавливаем отбор в динамическом списке формы
ПараметрСессии = Новый ПараметрВыбораДинамическогоСписка("ИдентификаторСессии", ИдентификаторСессии);
Список.Параметры.УстановитьЗначениеПараметра("ИдентификаторСессии", ИдентификаторСессии);

Важный момент: Не забывайте очищать буфер при закрытии формы или использовать регламентное задание, которое будет раз в сутки удалять «осиротевшие» записи старше определенного времени.

---

Решение 2. Программная генерация запроса через ОБЪЕДИНИТЬ ВСЕ

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

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

Рассмотрим пример программной генерации текста запроса:


ТекстЗапроса = "";
Индекс = 0;

Для Каждого СтрокаТЗ Из МояТаблицаЗначений Цикл
    Если Индекс > 0 Тогда
        ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
    КонецЕсли;
    
    // Формируем строку выбора с жестко прописанными литералами
    ТекстЗапроса = ТекстЗапроса + 
        "ВЫБРАТЬ """ + СтрокаТЗ.НоменклатураНаименование + """ КАК Номенклатура, " + 
        СтрокаТЗ.Количество + " КАК Количество";
        
    Индекс = Индекс + 1;
КонецЦикла;

// Присваиваем сгенерированный текст динамическому списку
Если Индекс > 0 Тогда
    Список.ТекстЗапроса = ТекстЗапроса;
Иначе
    // Если таблица пуста, выводим пустую схему
    Список.ТекстЗапроса = "ВЫБРАТЬ """" КАК Номенклатура, 0 КАК Количество ГДЕ ЛОЖЬ";
EndIf;

Внимание: Этот метод имеет жесткие ограничения. При превышении определенного объема строк (более 1500–2000) СУБД вернет ошибку из-за чрезмерной длины SQL-запроса или слишком большого количества объединений. Также стандартный полнотекстовый поиск по такому динамическому списку может работать некорректно.

---

Решение 3. Использование стандартной таблицы формы и свойства ОтборСтрок

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

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

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


// Создаем структуру отбора
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("СтатусДанных", ПредопределенноеЗначение("Перечисление.Статусы.Активен"));
ПараметрыОтбора.Вставить("Склад", ВыбранныйСклад);

// Применяем фильтр к таблице на форме
Элементы.МояТаблицаФормы.ОтборСтрок = Новый ФиксированнаяСтруктура(ПараметрыОтбора);

Преимущества: Фильтрация выполняется мгновенно прямо в оперативной памяти на клиенте без отправки запросов к серверу СУБД. При этом данные остаются полностью доступными для изменения.

Недостатки: Свойство ОтборСтрок поддерживает только жесткое сравнение на равенство («И»). Вы не сможете использовать сложные условия (например, «больше», «в списке» или логическое «ИЛИ»).

---

Решение 4. Вывод таблицы через Систему компоновки данных (СКД)

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

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

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

ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ВнешняяТаблицаДанных", МояТаблицаЗначений);

КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки());

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);

// Выводим результат в табличный документ на форме
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Элементы.РезультатОтчета);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

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

---

Решение 5. Событие ПриПолученииДанныхНаСервере

Иногда возникает промежуточная задача: базовые данные хранятся в БД (их легко вывести динамическим списком), но некоторые расчетные колонки формируются сложными программными алгоритмами «на лету» в оперативной памяти.

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

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

Рассмотрим пример кода:


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

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

---

Резюме: какой метод выбрать?

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

← На главную