Почему не срабатывает программный отбор строк 'В группе' для табличной части в обычных формах 1С 8.3?

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

При разработке и доработке функционала в 1С на обычных формах часто возникает необходимость программно накладывать отборы на табличные части. Один из распространённых сценариев — это отбор по контрагенту или другим иерархическим справочникам с использованием условия "В группе". Однако, как показывает практика, применение такого отбора через свойство ОтборСтрок для табличного поля не всегда работает ожидаемым образом.

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

Анализируем исходную проблему: Программный отбор по контрагенту "В группе"

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


ОтборСтрок = ЭлементФормыТаблица.ОтборСтрок;

Для каждого СтрокаОтбора Из НовыйОсновнойОтбор Цикл
    НайденнаяСтрокаОтбора = ОтборСтрок.Найти(СтрокаОтбора.Имя);
    
    НайденнаяСтрокаОтбора.Использование = СтрокаОтбора.Использование;
    НайденнаяСтрокаОтбора.ВидСравнения = СтрокаОтбора.ВидСравнения;
    НайденнаяСтрокаОтбора.Значение = СтрокаОтбора.Значение;
    НайденнаяСтрокаОтбора.Использование = СтрокаОтбора.Использование; // Возможно, опечатка, повторяющееся присваивание
КонецЦикла;

При этом, как сообщил разработчик, отбор на текстовых полях с использованием ВидСравнения.Содержит или на полях с простым равенством (ВидСравнения.Равно) срабатывал корректно. Однако, когда для поля "Контрагент" (которое имеет составной тип) устанавливался ВидСравнения.ВГруппе и в Значение передавалась группа контрагентов, список документов не изменялся, указывая на то, что отбор не применялся.

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

Выясняем причину: Ограничения ОтборСтрок в Обычных Формах

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

Мы видим, что ОтборСтрок в обычных формах предназначен, в первую очередь, для более простых видов сравнения или для отбора по плоским спискам значений. Когда мы пытаемся применить иерархический отбор (например, "В группе" или "Не в группе"), механизм ОтборСтрок не справляется с обработкой такой логики для табличных полей, которые не являются динамическими списками.

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

Решения и обходные пути для отбора "В группе" в обычных формах

Поскольку прямой отбор "В группе" не работает, мы рассмотрим несколько эффективных обходных путей, которые позволят достичь требуемого результата.

1. Использование ВидСравнения.ВСписке (В списке)

Это один из наиболее часто используемых и надежных методов. Вместо того чтобы пытаться передать группу в Значение, мы можем предварительно получить полный список всех элементов, входящих в эту группу (и, возможно, во все ее подгруппы), а затем использовать этот список для отбора.

  1. Получаем список всех элементов группы. Для этого мы можем использовать запрос к справочнику или рекурсивный обход иерархии.

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

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

    Этот подход работает надежно, так как ВСписке является поддерживаемым видом сравнения для ОтборСтрок в обычных формах.

2. Программная фильтрация с использованием дополнительной булевой колонки

Этот метод применим, когда нам нужна более сложная логика отбора, которую трудно выразить стандартными ВидСравнения, или когда мы хотим иметь полный контроль над фильтрацией. Суть метода заключается в добавлении в табличную часть служебной булевой колонки, которую мы программно заполняем значением "Истина" для тех строк, которые соответствуют нашим критериям отбора, и "Ложь" для остальных. Затем мы накладываем отбор по этой булевой колонке.

  1. Добавляем булеву колонку в табличную часть. Мы можем это сделать программно при формировании табличной части или заранее в конфигураторе, если табличная часть является реквизитом обработки.

    
    // Предположим, что ЭлементФормыТаблица.Значение - это ТаблицаЗначений
    Если Не ЭлементФормыТаблица.Значение.Колонки.Найти("ПринадлежитОтбору") = Неопределено Тогда
        ЭлементФормыТаблица.Значение.Колонки.Добавить("ПринадлежитОтбору", Новый ОписаниеТипов("Булево"));
    КонецЕсли;
    
  2. Перебираем строки и заполняем булеву колонку. Для каждой строки табличной части мы проверяем, соответствует ли она условиям отбора (включая проверку вхождения контрагента в группу) и устанавливаем соответствующее значение в новую колонку.

    
    // Предположим, что ГруппаКонтрагентов - это группа, по которой мы хотим отбирать.
    // Используем Запрос.Текст = "ВЫБРАТЬ Контрагенты.Ссылка ИЗ Справочник.Контрагенты КАК Контрагенты ГДЕ Контрагенты.Ссылка В ИЕРАРХИИ(&Группа)"
    // для создания СписокЭлементовГруппы, как в предыдущем примере.
            
    Для Каждого СтрокаТЧ Из ЭлементФормыТаблица.Значение Цикл
        СтрокаТЧ.ПринадлежитОтбору = Ложь;
        Если СтрокаТЧ.Контрагент <> Неопределено И СписокЭлементовГруппы.НайтиЗначение(СтрокаТЧ.Контрагент) <> Неопределено Тогда
            СтрокаТЧ.ПринадлежитОтбору = Истина;
        КонецЕсли;
    КонецЦикла;
    
  3. Устанавливаем отбор по булевой колонке.

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

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

3. Формирование данных с помощью запроса или построителя отчета на этапе загрузки

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

  1. Использование прямого запроса. Если данные вашей табличной части формируются из базы данных, вы можете использовать объект Запрос и в его тексте применить условие "В ИЕРАРХИИ".

    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    ДокументыЭДО.НомерДокумента,
    |    ДокументыЭДО.ДатаДокумента,
    |    ДокументыЭДО.Контрагент
    |ИЗ
    |    РегистрСведений.ДокументыЭДО КАК ДокументыЭДО // Пример источника данных
    |ГДЕ
    |    ДокументыЭДО.Контрагент В ИЕРАРХИИ(&ГруппаКонтрагентов)";
            
    Запрос.УстановитьПараметр("ГруппаКонтрагентов", ГруппаКонтрагентов);
            
    РезультатЗапроса = Запрос.Выполнить().Выгрузить();
    ЭлементФормыТаблица.Значение.Загрузить(РезультатЗапроса);
    

    Этот метод является наиболее оптимальным, так как отбор выполняется на стороне СУБД, что значительно быстрее, чем программный перебор в 1С.

  2. Использование Построителя Отчета. Разработчик в исходной теме предпринял попытку использовать ПостроительОтчета, но столкнулся с проблемой: "В группе" для выгруженной ТаблицыЗначений возвращало пустую таблицу (сообщение 25).

    Почему это произошло? ПостроительОтчета может работать с различными источниками данных, включая ОписаниеИсточникаДанных, созданное из ТаблицыЗначений. Однако, когда ПостроительОтчета работает с плоской ТаблицейЗначений, он не имеет встроенного понимания иерархии справочников. Чтобы ПостроительОтчета мог эффективно использовать отбор "В группе" или "В иерархии", его ИсточникДанных должен быть более "умным", например, Запрос, который уже включает иерархический отбор в своем тексте.

    Если мы хотим использовать ПостроительОтчета для уже загруженной в память ТаблицыЗначений, нам, скорее всего, придется применить те же принципы, что и в первом методе – заранее сформировать СписокЗначений всех элементов группы и передать его в ПостроительОтчета.Отбор с ВидСравнения.ВСписке.

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

    В этом случае, ПостроительОтчета будет работать как удобный инструмент для применения отборов к данным в памяти, но логика "В группе" по-прежнему будет преобразована в "В списке" перед применением.

Важные замечания

  1. Составной тип данных: Хотя составной тип данных поля "Контрагент" в данном случае не является основной причиной проблемы с "В группе" (поскольку основная причина – это ограничение платформы на ОтборСтрок), всегда стоит учитывать, что работа с составными типами требует внимательности. В некоторых случаях платформа может быть неспособна однозначно сравнивать или фильтровать значения составного типа без дополнительных указаний. Однако, для ВидСравнения.Равно или ВидСравнения.ВСписке обычно проблем не возникает, если значение точно соответствует одному из типов, входящих в составной.
  2. Производительность: Методы, требующие программного перебора строк табличной части (например, с булевой колонкой), могут значительно снижать производительность при работе с большим количеством строк. Всегда предпочтительнее выполнять фильтрацию на уровне запроса к базе данных, если это возможно.
  3. Видимость колонки: Для корректной работы ОтборСтрок колонка, по которой производится отбор, должна присутствовать в составе табличного поля. Она не обязательно должна быть видимой для пользователя, но должна существовать в коллекции колонок табличного поля.

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

← На главную