Как правильно установить и очистить программный отбор в динамическом списке 1С?

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

При разработке на управляемых формах 1С часто возникает задача: фильтровать данные в динамическом списке по нажатию кнопки или изменению флажка. Например, на форме есть флажок СкрыватьАрхивные. Если он установлен, необходимо скрыть документы, у которых заполнена ДатаВыдачи. Если флажок снят — показать все документы без ограничений.

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

Анализ ошибки: почему отбор не очищается?

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


&НаКлиенте
Процедура СкрытьПоказатьАрхивные()
    Если ЭтотОбъект.СкрыватьАрхивные = Истина Тогда
        Отбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных")); 
        Отбор.ЛевоеЗначение = Список.Отбор.ДоступныеПоляОтбора.Элементы.Найти("ДатаВыдачи").Поле;
        Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.НеЗаполнено;
        Отбор.Использование = Истина;
    Иначе
        Отбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных")); 
        Отбор.ЛевоеЗначение = Список.Отбор.ДоступныеПоляОтбора.Элементы.Найти("ДатаВыдачи").Поле;
        Отбор.Использование = Ложь;
    КонецЕсли;
КонецПроцедуры

Выясним причину такого поведения. При каждом вызове процедуры выполняется метод Список.Отбор.Элементы.Добавить(). Платформа 1С не умеет автоматически находить старый отбор по полю и перезаписывать его. Вместо этого она добавляет новое условие отбора.

После нескольких кликов по флажку в коллекции отборов накапливаются десятки дублирующихся условий. Когда мы попадаем в ветку Иначе, код снова создает новый элемент отбора и устанавливает для него Использование = Ложь. При этом самые первые, ранее созданные отборы, у которых Использование = Истина, остаются активными в списке. Из-за этого визуально отбор не снимается.

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

Способ 1: Использование процедур БСП (Библиотеки стандартных подсистем)

Если ваша конфигурация разработана на базе БСП (а это большинство современных типовых решений 1С), изобретать велосипед не нужно. В общих модулях уже предусмотрены процедуры для безопасной работы с отборами динамических списков, которые сами проверяют наличие дублей и обновляют существующие элементы.

Применим функции из модуля ОбщегоНазначенияКлиентСервер:


&НаКлиенте
Процедура СкрытьПоказатьАрхивные()
    Если ЭтотОбъект.СкрыватьАрхивные = Истина Тогда    
        ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(
            Список.Отбор, 
            "ДатаВыдачи", 
            , 
            ВидСравненияКомпоновкиДанных.НеЗаполнено, 
            , 
            Истина
        );        
    Иначе    
        ОбщегоНазначенияКлиентСервер.УдалитьЭлементыГруппыОтбора(Список.Отбор, "ДатаВыдачи");            
    КонецЕсли;
КонецПроцедуры

Важно: в разных версиях БСП названия методов могут немного отличаться. Часто используются аналогичные методы, например: УстановитьЭлементОтбораДинамическогоСписка() и УдалитьЭлементыГруппыОтбораДинамическогоСписка(). Суть остается прежней: система берет на себя поиск нужного поля и его удаление.

Способ 2: Очистка отбора классическими средствами (без БСП)

Если конфигурация не использует БСП, мы можем написать логику поиска и удаления отбора самостоятельно. Коллекция Список.Отбор.Элементы не имеет прямого метода поиска по имени поля, поэтому нам потребуется обойти ее в цикле.

Рассмотрим пример, в котором мы ищем отбор по левому значению и удаляем его:


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

    // Если флаг установлен - добавляем отбор заново
    Если ЭтотОбъект.СкрыватьАрхивные = Истина Тогда
        НовыйОтбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных")); 
        НовыйОтбор.ЛевоеЗначение = ПолеПоиска;
        НовыйОтбор.ВидСравнения  = ВидСравненияКомпоновкиДанных.НеЗаполнено;
        НовыйОтбор.Использование = Истина;
    КонецЕсли;

КонецПроцедуры

Также существует небольшая хитрость, упрощающая поиск. При программном добавлении отбора ему можно задать уникальное Представление, например "ПрограммныйОтборАрхив". Тогда в цикле достаточно будет проверять строку: Если ЭлементОтбора.Представление = "ПрограммныйОтборАрхив" Тогда .... Это делает код более читаемым и исключает случайное удаление пользовательских отборов по этому же полю.

Способ 3: Использование параметров в произвольном запросе (Альтернативный и оптимальный вариант)

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

Для этого в текст запроса динамического списка добавляется условие:


ГДЕ (&ПоказыватьВсе = ИСТИНА ИЛИ ДатаВыдачи ЕСТЬ NULL)
// Если ДатаВыдачи в базе всегда заполнена пустой датой, а не NULL, 
// тогда условие будет: ИЛИ ДатаВыдачи = ДАТАВРЕМЯ(1,1,1)

Тогда программный код на клиенте сократится до одной простой строки установки параметра:


&НаКлиенте
Процедура СкрытьПоказатьАрхивные()
    // Параметр &ПоказыватьВсе будет равен Истина, когда СкрыватьАрхивные = Ложь
    Список.Параметры.УстановитьЗначениеПараметра("ПоказыватьВсе", Не ЭтотОбъект.СкрыватьАрхивные);
КонецПроцедуры

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

Важные нюансы работы с программными отборами

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

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

← На главную