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