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

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

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

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

Выясняем причину: почему простой отбор не работает на клиенте?

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

С объектом типа РегистрСведенийНаборЗаписей ситуация обстоит иначе. Это полноценный прикладной объект, предназначенный для чтения и записи данных в базу. На форме управляемого приложения он представлен типом данных ДанныеФормыКоллекция. Когда мы изменяем свойства отбора на клиенте, мы всего лишь меняем метаданные отбора в структуре данных формы. Самого обращения к СУБД за новыми данными не происходит. Чтобы платформа заполнила коллекцию актуальными записями, набор необходимо принудительно прочитать из базы данных на стороне сервера.

Пошаговое решение: программное чтение набора записей с отбором

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

Разберем по шагам код серверной процедуры, которая решает эту задачу контекстным методом:


&НаСервере
Процедура ОбновитьНаборЗаписейНаСервере(Знач ТекущийКвотер)

    // Шаг 1. Преобразуем данные формы (коллекцию) в полноценный объект "Набор записей"
    НаборЗаписейОбъект = ДанныеФормыВЗначение(Квотирование, Тип("РегистрСведенийНаборЗаписей.Квотирование"));
    
    // Шаг 2. Устанавливаем необходимый отбор
    НаборЗаписейОбъект.Отбор.Квотер.Установить(ТекущийКвотер);
    
    // Шаг 3. Читаем данные из базы данных под установленный отбор
    НаборЗаписейОбъект.Прочитать();
    
    // Шаг 4. Возвращаем заполненный объект обратно в структуру данных формы
    ЗначениеВДанныеФормы(НаборЗаписейОбъект, Квотирование);

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

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

Оптимизация серверных вызовов: контекстный vs неконтекстный вызов

Использование контекстного метода &НаСервере в формах с большим объемом данных может приводить к задержкам в интерфейсе, так как при каждом вызове на сервер передается весь контекст формы (все реквизиты, элементы управления, структура формы). Для повышения производительности мы можем и должны использовать неконтекстный серверный вызов &НаСервереБезКонтекста.

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


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

&НаСервереБезКонтекста
Процедура ОбновитьНаборНаСервереБезКонтекста(БуферНабора, Знач пТекСтрокаИД)
    
    // Преобразуем переданные данные формы в объект
    Набор = ДанныеФормыВЗначение(БуферНабора, Тип("РегистрСведенийНаборЗаписей.Квотирование"));
    
    // Устанавливаем отбор по измерению "СтрокаИД"
    Набор.Отбор.СтрокаИД.Установить(пТекСтрокаИД);
    
    // Читаем данные из базы
    Набор.Прочитать();
    
    // Возвращаем данные обратно в структуру формы
    ЗначениеВДанныеФормы(Набор, БуферНабора);
    
КонецПроцедуры

Использование метода КопироватьДанныеФормы на клиенте позволяет безопасно перенести обновленную структуру данных из локальной переменной (буфера) в рабочий реквизит управляемой формы, минимизируя нагрузку на сетевой трафик между клиентом и сервером.

Опасность обработчика события «ПриАктивизацииСтроки» и способы борьбы с ней

Разработчикам важно помнить, что вызов серверных методов (даже неконтекстных) непосредственно внутри обработчика ПриАктивизацииСтроки таит в себе серьезные архитектурные риски:

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

Для предотвращения этих проблем рассмотрим два проверенных временем решения (Best Practices).

Решение А: Использование обработчика ожидания (Debounce)

Этот паттерн позволяет выполнять запрос к серверу только тогда, когда пользователь остановил свой выбор на строке и не перемещает курсор в течение короткого промежутка времени (например, 0.2–0.3 секунды). Посмотрим, как это реализовать:


&НаКлиенте
Перем ТекущийИДДляОбработки; // Переменная для хранения промежуточного ИД

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

&НаКлиенте
Процедура ПодключитьОбновлениеНабораЗаписей()
    
    // Вызываем серверный метод только после паузы в действиях пользователя
    БуферНабора = Квотирование;
    ОбновитьНаборНаСервереБезКонтекста(БуферНабора, ТекущийИДДляОбработки);
    КопироватьДанныеФормы(БуферНабора, Квотирование);
    
КонецПроцедуры

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

Решение Б: Проверка изменения ключевого значения на клиенте

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


&НаКлиенте
Перем ПоследнийОбработанныйИД;

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

Альтернативный вариант: использование Таблицы значений

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

При использовании ТЗ схема работы выглядит следующим образом:

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

Рассмотрим пример программной записи измененных данных из Таблицы Значений формы обратно в регистр сведений:


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

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

← На главную