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

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

Каждый разработчик 1С рано или поздно сталкивается с задачей фильтрации строк табличной части на форме по какому-либо реквизиту связанного справочника или документа. Например, в документе есть табличная часть со списком заказов покупателей, и нам необходимо отфильтровать этот список по конкретной организации, которая хранится внутри каждого заказа. Казалось бы, решение лежит на поверхности: открыть Синтаксис-помощник, найти описание объекта Отбор и использовать обращение через точку, например, ЗаказПокупателя.Организация. Справка платформы услужливо подсказывает нам примеры вида Контрагент.Код.

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

Важное теоретическое отступление: почему Синтаксис-помощник нас дезинформирует?

Объект Отбор в платформе 1С является универсальным. Он применяется во множестве различных механизмов: в построителе отчетов, в системе компоновки данных (СКД), в динамических списках и критериях отбора. В этих механизмах обращение к реквизитам через точку полностью поддерживается, так как платформа на уровне СУБД выполняет полноценное соединение таблиц (SQL JOIN).

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

Теперь, когда мы понимаем природу этого ограничения, рассмотрим эффективные обходные пути.

Решение №1. Предварительный запрос + Отбор «В списке» (для обычных форм)

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

Проанализируем этот алгоритм по шагам:

  1. При изменении фильтра на форме (например, при выборе организации) мы делаем запрос к базе данных.
  2. В запросе мы выбираем все документы ЗаказПокупателя, у которых реквизит Организация равен выбранной нами организации.
  3. Результат выполнения запроса мы выгружаем в массив или список значений.
  4. Устанавливаем отбор по колонке ЗаказПокупателя табличной части, используя вид сравнения ВСписке.

Посмотрим на практический пример реализации этого алгоритма в коде:


Процедура ФильтрОрганизацияПриИзменении(Элемент)

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

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

    // Шаг 4. Устанавливаем визуальный отбор в табличной части
    Отбор = ЭлементыФормы.Заказы.ОтборСтрок;
    
    Отбор.ЗаказПокупателя.ВидСравнения = ВидСравнения.ВСписке;
    Отбор.ЗаказПокупателя.Значение = СписокЗаказов;
    Отбор.ЗаказПокупателя.Использование = Истина;

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

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

Решение №2. Добавление реквизита в метаданные табличной части

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

Разберем по шагам этот метод:

  1. В конфигураторе открываем свойства нашего документа, переходим на вкладку «Данные» и находим нужную табличную часть.
  2. Добавляем новый реквизит табличной части. Назовем его Организация с типом ссылки на соответствующий справочник.
  3. В обработчике события ПриИзменении для поля ЗаказПокупателя в табличной части пишем код автоматического заполнения новой колонки.
  4. При проведении или записи старых документов пишем обработку, которая один раз заполнит этот реквизит для исторических данных.

Посмотрим на пример кода для автозаполнения новой колонки при интерактивном выборе заказа пользователем:


Процедура ЗаказыЗаказПокупателяПриИзменении(Элемент)
    
    СтрокаТЧ = ЭлементыФормы.Заказы.ТекущаяСтрока;
    Если СтрокаТЧ <> Неопределено Тогда
        // Получаем организацию напрямую из выбранного заказа покупателя
        СтрокаТЧ.Организация = СтрокаТЧ.ЗаказПокупателя.Организация;
    КонецЕсли;
    
КонецПроцедуры

После этого отбор на форме устанавливается классическим и максимально простым кодом:


ЭлементыФормы.Заказы.ОтборСтрок.Организация.ВидСравнения = ВидСравнения.Равно;
ЭлементыФормы.Заказы.ОтборСтрок.Организация.Значение = ВыбраннаяОрганизация;
ЭлементыФормы.Заказы.ОтборСтрок.Организация.Использование = Истина;

Решение №3. Добавление реквизита на форму (для управляемых форм)

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

Чтобы не засорять реальные таблицы базы данных избыточными реквизитами (как в Решении №2), мы можем использовать отличную возможность управляемых форм — создание реквизитов, которые живут исключительно на форме в памяти клиента и сервера.

Выясним, как это реализовать:

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

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Заполняем виртуальную колонку формы данными из базы
    Для Каждого СтрокаТЧ Из Объект.Заказы Цикл
        СтрокаТЧ.Организация = СтрокаТЧ.ЗаказПокупателя.Организация;
    КонецЦикла;
    
КонецПроцедуры

Обязательно дублируем заполнение виртуального реквизита при изменении заказа пользователем на клиенте:


&НаКлиенте
Процедура ЗаказыЗаказПокупателяПриИзменении(Элемент)
    
    СтрокаТЧ = Элементы.Заказы.ТекущиеДанные;
    Если СтрокаТЧ <> Неопределено Тогда
        // Получаем организацию (может потребоваться простой серверный вызов)
        СтрокаТЧ.Организация = ПолучитьОрганизациюЗаказа(СтрокаТЧ.ЗаказПокупателя);
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьОрганизациюЗаказа(Знач ЗаказСсылка)
    Возврат ЗаказСсылка.Организация;
КонецФункции

Теперь мы можем применить к таблице формы быструю и легкую фильтрацию по нашей новой колонке формы:


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

Этот метод является наиболее экологичным и рекомендуемым стандартом разработки для управляемых форм 1С. Мы получаем чистую базу данных без лишних реквизитов в метаданных и мгновенно работающий отбор на клиенте.

Решение №4. Динамический список с произвольным запросом

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

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


&НаКлиенте
Процедура ПриИзмененииОрганизации(Элемент)

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

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

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

Подведем итоги:

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

← На главную