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

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

При разработке в расширениях 1С программисты часто сталкиваются с необходимостью автоматического заполнения реквизитов шапки документа (или самой формы) при изменении данных в табличной части. Классический пример — заполнение ценовой группы в зависимости от выбранной номенклатуры.

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

В чем заключалась ошибка исходного кода?

В самом начале обсуждения автор темы попытался решить задачу «в лоб» через контекстный вызов сервера и преобразование реквизитов формы в объект базы данных. Код выглядел следующим образом:


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

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

  1. Огромные накладные расходы. Метод РеквизитФормыВЗначение полностью сериализует все данные формы (включая всю табличную часть со всеми строками), отправляет их на сервер, создает там полноценный прикладной объект в памяти, а затем метод ЗначениеВРеквизитФормы проделывает обратную процедуру. Если в табличной части документа будет несколько сотен строк, этот вызов вызовет заметное «зависание» интерфейса у пользователя.
  2. Проблема реквизитов расширения. Если новый реквизит Расш_ЦеноваяГруппа был добавлен только на форму как экранный элемент, а не в сам объект конфигурации внутри расширения, то при создании прикладного объекта через РеквизитФормыВЗначение платформа просто проигнорирует это поле, так как в метаданных исходного объекта базы данных его не существует. В результате обратная конвертация сотрет значение на форме.
  3. Использование контекстного вызова &НаСервере. При таком объявлении процедуры на сервер передается весь контекст формы, что сильно перегружает сетевой трафик и замедляет работу системы.

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

Оптимальное решение: Использование безконтекстного вызова

Поскольку на клиенте мы не можем обращаться к реквизитам ссылочных типов через точку (например, написать напрямую ТекущиеДанные.Номенклатура.ЦеноваяГруппа запрещено трехзвенной архитектурой), нам все же придется обратиться к серверу. Но сделаем мы это максимально «дешево» для производительности системы.

Рассмотрим по шагам правильную реализацию с использованием директивы &НаСервереБезКонтекста:


&НаКлиенте
Процедура Расш1_ТоварыПриОкончанииРедактированияПосле(Элемент, НоваяСтрока, ОтменаРедактирования)
    
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем ценовую группу через легкий серверный запрос и сразу присваиваем ее на клиенте
    Объект.Расш_ЦеноваяГруппа = Расш_ПолучитьЦеновуюГруппу(ТекущиеДанные.Номенклатура);
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция Расш_ПолучитьЦеновуюГруппу(Номенклатура)
    
    // Метод выполняется на сервере, но не тащит с собой контекст формы. 
    // На вход передается только ссылка на номенклатуру (несколько байт).
    Возврат Номенклатура.ЦеноваяГруппа;
    
КонецФункции

Почему этот код работает мгновенно и надежно?

Как сделать решение еще лучше?

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

1. Правильный выбор события формы

В исходном примере автор использует событие всей таблицы ТоварыПриОкончанииРедактирования. Это событие генерируется только тогда, когда пользователь закончил строку целиком (например, нажал Enter или перешел на другую строку). Если пользователь выберет номенклатуру, а затем просто кликнет мышкой в другую колонку этой же строки, значение в шапке документа не обновится сразу.

Гораздо правильнее перехватывать событие ПриИзменении конкретного поля Номенклатура в табличной части (например, событие ТоварыНоменклатураПриИзменении). В таком случае ценовая группа в шапке заполнится немедленно в момент выбора товара в форме выбора или автоподбора.

2. Использование функций БСП (Библиотеки стандартных подсистем)

Если вы работаете в современной типовой конфигурации (например, КА 2.4, ERP 2.5, УТ 11 и др.), вам даже не обязательно писать свою серверную функцию в расширении. В БСП есть готовый программный интерфейс для быстрого получения реквизитов объектов без контекста.

Вы можете вызвать стандартный метод БСП прямо из клиентского кода:


&НаКлиенте
Процедура Расш1_ТоварыНоменклатураПриИзмененииПосле(Элемент)
    
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        // Используем стандартную функцию БСП, доступную на клиенте
        Объект.Расш_ЦеноваяГруппа = ОбщегоНазначенияВызовСервера.ЗначениеРеквизитаОбъекта(ТекущиеДанные.Номенклатура, "ЦеноваяГруппа");
    КонецЕсли;
    
КонецПроцедуры

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

← На главную