Как программно настроить отбор по тексту запроса и сохранить настройки компоновщика в ХранилищеЗначения на управляемой форме 1С

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

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

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

  1. Как динамически (в коде) создать схему компоновки данных по произвольному тексту запроса и связать её с компоновщиком настроек на управляемой форме для отображения доступных полей отбора.
  2. Как сохранить и восстановить структуру настроек этого компоновщика для каждой строки табличной части прикладного объекта (например, справочника или документа), если соответствующий реквизит имеет тип ХранилищеЗначения.

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

Часть 1. Динамическое создание схемы компоновки данных (СКД) по тексту запроса

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

Рассмотрим процедуру создания динамической схемы компоновки данных. Создадим пустую схему, добавим источник данных и набор данных типа "Запрос":


// Создаем объект схемы компоновки данных
СхемаКомпоновкиДанных = Новый СхемаКомпоновкиДанных;

// Добавляем источник данных (локальный)
ИсточникДанных = СхемаКомпоновкиДанных.ИсточникиДанных.Добавить();
ИсточникДанных.Имя = "ИсточникДанных1";
ИсточникДанных.ТипИсточникаДанных = "Local";

// Добавляем набор данных - запрос
НаборДанных = СхемаКомпоновкиДанных.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
НаборДанных.Имя = "НаборДанных1";
НаборДанных.ИсточникДанных = ИсточникДанных.Имя;
НаборДанных.Запрос = ТекстЗапроса; // Передаем наш произвольный текст запроса

Важная деталь об автозаполнении полей: Обратите внимание на свойство АвтоЗаполнениеДоступныхПолей. В обсуждении на форуме высказывалось мнение, что его лучше устанавливать в значение Ложь, если в запросе нет фигурных скобок {}. Это критическое заблуждение! Если вы установите данное свойство в значение Ложь, платформа не станет автоматически считывать структуру полей из секции ВЫБРАТЬ вашего запроса. В результате список доступных полей для отбора в компоновщике окажется пустым. Для автоматического заполнения доступных полей по запросу свойство АвтоЗаполнениеДоступныхПолей обязательно должно быть установлено в значение Истина (что является поведением по умолчанию).

После создания схемы ее необходимо "скормить" реквизиту нашей управляемой формы с типом КомпоновщикНастроекКомпоновкиДанных. Напрямую присвоить схему компоновщику на клиенте или сервере нельзя — инициализация выполняется через временное хранилище и специальный объект-источник доступных настроек.

Реализуем процедуру инициализации компоновщика на сервере:


&НаСервере
Процедура ИнициализироватьКомпоновщикНаСервере(ТекстЗапроса)

    // 1. Создаем динамическую схему (код из предыдущего шага)
    СхемаКомпоновкиДанных = Новый СхемаКомпоновкиДанных;
    
    ИсточникДанных = СхемаКомпоновкиДанных.ИсточникиДанных.Добавить();
    ИсточникДанных.Имя = "ИсточникДанных1";
    ИсточникДанных.ТипИсточникаДанных = "Local";
    
    НаборДанных = СхемаКомпоновкиДанных.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
    НаборДанных.Имя = "НаборДанных1";
    НаборДанных.ИсточникДанных = ИсточникДанных.Имя;
    НаборДанных.АвтоЗаполнениеДоступныхПолей = Истина; // Обязательно Истина!
    НаборДанных.Запрос = ТекстЗапроса;
    
    // Очистим настройки по умолчанию для гарантии чистоты структуры
    СхемаКомпоновкиДанных.НастройкиПоУмолчанию.Отбор.Элементы.Очистить();
    
    // 2. Помещаем схему во временное хранилище, привязывая к УИД формы
    URLСхемыКомпоновкиДанных = ПоместитьВоВременноеХранилище(СхемаКомпоновкиДанных, УникальныйИдентификатор);
    
    // 3. Инициализируем компоновщик настроек (реквизит формы "Компоновщик")
    ИсточникНастроек = Новый ИсточникДоступныхНастроекКомпоновкиДанных(URLСхемыКомпоновкиДанных);
    Компоновщик.Инициализировать(ИсточникНастроек);
    
    // 4. Загружаем дефолтные настройки
    Компоновщик.ЗагрузитьНастройки(СхемаКомпоновкиДанных.НастройкиПоУмолчанию);
    Компоновщик.Восстановить(СпособВосстановленияНастроекКомпоновкиДанных.Полное);

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

Для вывода настроек на управляемую форму добавьте таблицу формы и свяжите её путь к данным с реквизитом Компоновщик.ПользовательскиеНастройки (или Компоновщик.Настройки.Отбор для упрощенного варианта быстрого отбора). Пользователь получит полноценный и привычный интерфейс для выбора полей, видов сравнения и значений отбора.


Часть 2. Работа с реквизитами типа ХранилищеЗначения в табличных частях на УФ

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

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

Рассмотрим два способа обхода этого ограничения: быстрый способ (через преобразование объекта) и оптимальный с точки зрения производительности (best practice с использованием временного хранилища).

Решение 1. Быстрый способ через РеквизитФормыВЗначение

Чтобы получить доступ к реальным реквизитам базы данных (включая ХранилищеЗначения), необходимо конвертировать контекстный объект формы в полноценный прикладной объект на сервере. При этом важно конвертировать весь объект целиком, а не только его табличную часть.

Посмотрим на пример реализации сохранения настроек при изменении строки на форме:


&НаСервере
Процедура СохранитьНастройкиВСтрокуТЧНаСервере(НомерСтроки, АдресВременногоХранилища)

    // Преобразуем объект формы в полноценный прикладной объект СправочникОбъект
    СправочникОбъект = РеквизитФормыВЗначение("Объект");
    
    // Получаем структуру настроек из временного хранилища
    НастройкиДляСохранения = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
    
    // Находим нужную строку в реальном объекте (индексация с 0)
    ТекущаяСтрокаТЧ = СправочникОбъект.ТабличнаяЧасть[НомерСтроки - 1];
    
    // Записываем настройки в реквизит с типом ХранилищеЗначения
    ТекущаяСтрокаТЧ.СписокУсловий = Новый ХранилищеЗначения(НастройкиДляСохранения);
    
    // Возвращаем измененные данные обратно в структуру формы
    ЗначениеВРеквизитФормы(СправочникОбъект, "Объект");

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

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


Решение 2. Оптимальный паттерн (Best Practice) с использованием временного хранилища

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

Разберем данный алгоритм по шагам:

  1. Добавим в табличную часть формы (но не в метаданные справочника!) вспомогательную колонку АдресНастроек с типом Строка.
  2. При открытии формы (в обработчике ПриСозданииНаСервере) прочитаем существующие хранилища значений из базы данных, поместим их во временное хранилище и запишем полученные адреса в колонку АдресНастроек.
  3. При интерактивной работе на клиенте пользователь редактирует настройки. Измененный компоновщик сохраняется во временное хранилище по адресу, указанному в строке, без обращения к прикладному объекту БД.
  4. В обработчике ПередЗаписьюНаСервере мы один раз переносим все данные из адресов временного хранилища обратно в реквизиты табличной части объекта БД перед их непосредственной записью.

Рассмотрим программную реализацию этого паттерна.

Шаг 1. Инициализация адресов при открытии формы


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    // При открытии формы один раз считываем объект из БД
    Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть Цикл
        
        // Получаем значение из ХранилищаЗначения, сохраненного в БД
        НастройкиИзБД = СтрокаТЧ.СписокУсловий.Получить();
        
        Если НастройкиИзБД <> Неопределено Тогда
            // Помещаем во временное хранилище и записываем адрес в форму
            СтрокаТЧ.АдресНастроек = ПоместитьВоВременноеХранилище(НастройкиИзБД, УникальныйИдентификатор);
        КонецЕсли;
        
    КонецЦикла;

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

Шаг 2. Сохранение настроек в процессе редактирования формы

Когда пользователь выбирает строку табличной части или изменяет настройки в компоновщике, мы оперативно сохраняем их во временное хранилище на клиенте/сервере без перезаписи всего объекта:


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

&НаСервере
Процедура ПодключитьКомпоновщикПоАдресу(АдресНастроек)
    
    // Получаем сохраненные настройки из временного хранилища
    Настройки = ПолучитьИзВременногоХранилища(АдресНастроек);
    
    // Восстанавливаем настройки в компоновщике формы
    Компоновщик.ЗагрузитьНастройки(Настройки);
    
КонецПроцедуры

Когда пользователь меняет настройки в интерфейсе компоновщика, мы обновляем информацию во временном хранилище:


&НаКлиенте
Процедура ПриИзмененииНастроекКомпоновщика()

    ТекущиеДанные = Элементы.ТабличнаяЧасть.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем текущие настройки компоновщика и отправляем в хранилище
    АдресСХранилищем = СохранитьКомпоновщикВоВременноеХранилище(ТекущиеДанные.АдресНастроек);
    ТекущиеДанные.AddressНастроек = АдресСХранилищем;

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

&НаСервере
Функция СохранитьКомпоновщикВоВременноеХранилище(ТекущийАдрес)
    
    // Получаем настройки компоновщика (НастройкиКомпоновкиДанных)
    ТекущиеНастройки = Компоновщик.ПолучитьНастройки();
    
    // Перезаписываем данные по старому адресу или создаем новый адрес
    Если ЗначениеЗаполнено(ТекущийАдрес) Тогда
        ПоместитьВоВременноеХранилище(ТекущиеНастройки, ТекущийАдрес);
        Возврат ТекущийАдрес;
    Иначе
        Возврат ПоместитьВоВременноеХранилище(ТекущиеНастройки, УникальныйИдентификатор);
    EndIf;
    
КонецФункции

Шаг 3. Перенос данных в объект перед сохранением в базу данных

В самый последний момент, перед стандартной записью прикладного объекта на сервере, платформа предоставляет нам удобное событие ПередЗаписьюНаСервере. Здесь мы трансформируем адреса временного хранилища обратно в бинарные данные ХранилищаЗначения:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

    Для Каждого СтрокаФормы Из Объект.ТабличнаяЧасть Цикл
        
        // Находим соответствующую строку в записываемом прикладном объекте БД
        СтрокаОбъекта = ТекущийОбъект.ТабличнаяЧасть[Объект.ТабличнаяЧасть.Индекс(СтрокаФормы)];
        
        // Проверяем наличие временных данных на форме
        Если ЗначениеЗаполнено(СтрокаФормы.АдресНастроек) И ЭтоАдресВременногоХранилища(СтрокаФормы.АдресНастроек) Тогда
            
            // Извлекаем настройки из временного хранилища на сервере
            НастройкиИзХранилища = ПолучитьИзВременногоХранилища(СтрокаФормы.АдресНастроек);
            
            // Упаковываем настройки в ХранилищеЗначения
            СтрокаОбъекта.СписокУсловий = Новый ХранилищеЗначения(НастройкиИзХранилища);
            
        КонецЕсли;
        
    КонецЦикла;

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

Преимущества оптимального паттерна:

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

Используя предложенные методики, вы сможете легко перевести устаревшие формы отборов с использованием ПостроителяЗапроса обычного приложения на современные стандарты динамических настроек СКД в управляемом интерфейсе 1С.

← На главную