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