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