В процессе разработки в среде 1С:Предприятие 8.3 перед программистом часто возникает задача: обработать данные, которые пользователь самостоятельно отфильтровал на форме списка. Например, пользователь наложил сложный отбор по контрагенту, дате и ответственному, а затем нажал кнопку «Обработать все отобранные». Как получить именно эти строки, если динамический список (ДинамическийСписок) по своей природе считывает данные из базы порционно и не хранит всю выборку в оперативной памяти клиента?
Обычный обход строк здесь не поможет, а стандартное свойство ВыделенныеСтроки вернет только те элементы, которые физически выделены курсором мыши. В этой статье мы подробно разберем, как решить эту задачу изящно и быстро, используя встроенные механизмы Системы компоновки данных (СКД), а также проанализируем неочевидные нюансы и критические ошибки, которые могут возникнуть на практике.
Динамический список в управляемых формах 1С функционирует на базе Системы компоновки данных. Платформа предоставляет нам уникальную возможность программно получить «исполняемую» схему и «исполняемые» настройки компоновки. Это означает, что мы можем взять схему запроса нашего списка со всеми наложенными пользователем отборами, группировками и поисковыми строками, программно выполнить этот запрос на сервере и выгрузить результат в таблицу значений или массив.
Рассмотрим по шагам программный код, который решает эту задачу:
&НаСервере
Функция ПолучитьМассивОтобранныхСсылокНаСервере()
// Шаг 1. Получаем исполняемую схему и настройки динамического списка
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
// Шаг 2. Создаем компоновщик макета компоновки данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Компонуем макет для вывода в коллекцию значений
МакетКомпоновки = КомпоновщикМакета.Выполнить(
Схема,
Настройки,
,
,
Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений")
);
// Шаг 3. Инициализируем процессор компоновки данных
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
// Шаг 4. Выводим результат в таблицу значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ТаблицаРезультат = ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// Шаг 5. Выгружаем ключевое поле (например, Ссылку) в массив для последующей обработки
МассивСсылок = ТаблицаРезультат.ВыгрузитьКолонку("Ссылка");
Возврат МассивСсылок;
КонецФункции
Этот код отлично работает в большинстве стандартных сценариев, однако реальная разработка требует учета множества тонкостей. Разберем «подводные камни», с которыми вы столкнетесь при внедрении данного алгоритма.
При выгрузке динамического списка через СКД в результирующую таблицу значений попадают только те поля, которые выбраны для отображения на форме. Если пользователь или разработчик скрыл колонку Ссылка (установлено свойство Видимость = Ложь), СКД в целях оптимизации производительности может полностью исключить это поле из результирующего запроса к базе данных. В таком случае попытка выгрузить колонку вызовет ошибку обращения к несуществующему полю таблицы.
Чтобы гарантированно получить ключевые поля (такие как Ссылка), их необходимо принудительно добавить в выбранные поля исполняемых настроек компоновки перед выводом. Рассмотрим, как это сделать программно:
// Получаем настройки списка
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
// Проверяем, добавлено ли уже поле Ссылка в выбранные поля
ПолеСсылкаПрисутствует = Ложь;
Для Каждого ЭлементВыбора Из Настройки.Выбор.Элементы Цикл
Если ТипЗв = Тип("ДеятельноеПолеКомпоновкиДанных") И ЭлементВыбора.Поле = Новый ПолеКомпоновкиДанных("Ссылка") Тогда
ПолеСсылкаПрисутствует = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
// Если поля нет, добавляем его принудительно
Если НЕ ПолеСсылкаПрисутствует Тогда
НовоеПоле = Настройки.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
НовоеПоле.Поле = Новый ПолеКомпоновкиДанных("Ссылка");
НовоеПоле.Использование = Истина;
КонецЕсли;
Если ваш динамический список построен на базе независимого регистра сведений, у него физически отсутствует поле Ссылка. Для однозначной идентификации записей регистра нам потребуется получить значения всех его измерений, а также поле Период, если регистр является периодическим.
Проанализируем пример, в котором мы программно добавляем все ключевые поля для независимого периодического регистра сведений:
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
// Массив ключевых полей для идентификации записи регистра сведений
КлючевыеПоля = Новый Массив;
КлючевыеПоля.Добавить("Период");
КлючевыеПоля.Добавить("Организация");
КлючевыеПоля.Добавить("Номенклатура"); // Замените на ваши реальные измерения
Для Каждого ИмяПоля Из КлючевыеПоля Цикл
НовоеПоле = Настройки.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
НовоеПоле.Поле = Новый ПолеКомпоновкиДанных(ИмяПоля);
НовоеПоле.Использование = Истина;
КонецЦикла;
После вывода такого макета в таблицу значений мы сможем обойти полученные строки в цикле и работать с записями регистра по комбинации измерений.
Часто разработчики сомневаются: сработает ли данный метод, если пользователь применил не жесткий отбор в панели настроек, а воспользовался стандартной строкой поиска на форме списка (вызываемой по кнопкам Ctrl + F)?
Спешим вас успокоить — метод ПолучитьИсполняемыеНастройкиКомпоновкиДанных() полностью учитывает все интерактивные действия пользователя. В момент вызова метода платформа 1С автоматически анализирует поисковую строку, сопоставляет результаты с проиндексированными данными и преобразует поисковый запрос в отборы по ключевым полям. Таким образом, в результирующую выборку попадут строго те элементы, которые пользователь видит на экране.
Это самый критический «подводный камень» работы с динамическими списками. По своей сути динамический список считывает данные порциями (например, по 45 строк для отображения на экране). Но когда мы используем метод ПроцессорВывода.Вывести(), система пытается за один проход загрузить абсолютно все отобранные записи в оперативную память сервера приложений 1С.
Если пользователь случайно или намеренно сбросит все отборы в списке, в котором содержатся миллионы записей (например, в регистре сведений «Версии объектов» или крупном справочнике «Номенклатура»), сервер попытается выгрузить терабайты данных в память. Это гарантированно приведет к критическому зависанию рабочего процесса сервера 1С или его падению с системной ошибкой «Недостаточно памяти».
Для предотвращения этой проблемы рекомендуется программно ограничивать количество обрабатываемых записей или предварительно проверять их объем. Рассмотрим, как установить жесткий лимит количества строк:
// Ограничим вывод первыми 10 000 записей во избежание перегрузки сервера
Настройки.ПараметрыВывода.УстановитьЗначениеПараметра(
"ВыводитьПолномочия",
Ложь
);
// Устанавливаем ограничение количества записей непосредственно в настройках СКД
Настройки.ПараметрыВывода.УстановитьЗначениеПараметра(
"КоличествоЗаписей",
10000
);
Если записей заведомо больше лимита, рекомендуется выдать пользователю предупреждение с просьбой сузить отборы.
Если в вашей форме списка используется предопределенное событие ПриПолученииДанныхНаСервере(), в котором вы динамически рассчитываете виртуальные колонки (например, остатки товаров на складах в реальном времени или текстовые представления из внешних систем), помните: все эти данные будут проигнорированы при программной выгрузке через СКД.
Выгрузка через Систему компоновки данных обращается напрямую к базе данных и выполняет только исходный запрос динамического списка со всеми отборами. Код постобработки ячеек интерфейса формы в этом процессе не участвует. Если вам нужны рассчитанные данные, вам придется повторно вычислить их в коде обработки полученной таблицы значений.
Обратите внимание, что методы ПолучитьИсполняемуюСхемуКомпоновкиДанных() и ПолучитьИсполняемыеНастройкиКомпоновкиДанных() принадлежат контексту элемента формы (таблице), а не самому прикладному объекту динамического списка. Это означает, что их невозможно вызвать напрямую внутри фонового задания или в общем модуле без контекста формы.
Если вам требуется перенести длительный процесс обработки отобранных строк в фоновое задание (что является хорошим тоном разработки), вам необходимо:
ПоместитьВоВременноеХранилище().Регулярно применяя предложенный метод СКД и соблюдая правила предосторожности по работе с памятью, вы сможете создавать надежные, быстрые и удобные инструменты для пользователей ваших конфигураций 1С.