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