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

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

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

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

  1. Организация единой архитектуры создания и обновления программных реквизитов формы на разных этапах ее жизненного цикла.
  2. Оптимизация клиент-серверного взаимодействия и предотвращение тяжелых расчетов при закрытии формы (сценарий «Провести и закрыть»).
  3. Особенности интеграции табличных частей в расширения и решение проблемы «исчезающих» событий в палитре свойств Конфигуратора.

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

Часть 1. Единая методика обновления формы при работе с программными реквизитами

Представим типовую задачу: в расширении для документа (например, ЗаказКлиента в УТ 11.4) нам необходимо программно добавить в табличную часть Товары три колонки: Общий остаток, Резерв и Свободный остаток. Расчет этих показателей является тяжелой операцией, требующей обращения к регистрам накопления.

Основная сложность заключается в том, что данные должны быть актуальными в любой момент времени:

Рассмотрим стандартный жизненный цикл формы применительно к нашей задаче. Многие разработчики совершают ошибку, пытаясь создавать и заполнять реквизиты в событии ПриСозданииНаСервере. Но это событие вызывается один раз при инициализации формы. Если пользователь перечитает форму, событие ПриСозданииНаСервере повторно не вызовется. Если же мы перенесем логику в ПриЧтенииНаСервере, то столкнемся с проблемой: это событие вызывается только для уже существующих в базе данных объектов. Для новых документов (или документов, созданных копированием, пока они не записаны) событие ПриЧтенииНаСервере работать не будет.

Выясним причину этой проблемы. Платформа четко разделяет фазу **конструирования формы** (создание типов, реквизитов, элементов) и фазу **наполнения данными**. Программные реквизиты должны создаваться строго один раз, а заполняться — при каждом изменении контекста данных.

Для решения этой задачи применим **методику единого обновления данных формы**. Мы разделим логику на два независимых блока: конструирование структуры и расчет данных.

Шаг 1. Конструирование структуры реквизитов осуществляем строго в обработчике ПриСозданииНаСервере. Повторно вызывать этот код нельзя, так как метод платформы ИзменитьРеквизиты является крайне ресурсоемким и приводит к полной пересборке контекста формы.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Шаг 1.1: Создаем структуру реквизитов и элементов формы
    СоздатьПрограммныеКолонкиНаСервере();
    
    // Шаг 1.2: Первичное заполнение данными, если объект новый или получен копированием
    Если Объект.Ссылка.Пустая() Тогда
        ОбновитьОстаткиНаСервере();
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. Заполнение и расчет данных для существующих в базе документов привязываем к событию ПриЧтенииНаСервере. Это событие гарантированно срабатывает при открытии существующего документа и при вызове команды «Перечитать».


&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    
    // Так как реквизиты уже созданы в "ПриСозданииНаСервере",
    // здесь мы выполняем только их расчет и заполнение данными.
    ОбновитьОстаткиНаСервере(ТекущийОбъект);
    
КонецПроцедуры

Шаг 3. Реализуем обработчик ПослеЗаписиНаСервере. После того как транзакция записи успешно завершилась, документ сформировал новые движения в базе данных. Нам необходимо актуализировать наши программные колонки остатков на форме.


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

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


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

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

Часть 2. Как избежать тяжелых расчетов при действии «Провести и закрыть»

Перейдем ко второму вопросу. При интерактивной работе в документе пользователь часто нажимает кнопку **«Провести и закрыть»**. В этот момент платформа выполняет следующую последовательность действий:

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

Проанализируем эту цепочку. На шаге 2 в событии ПослеЗаписиНаСервере (или на шаге 3 в клиентском ПослеЗаписи) срабатывает наш тяжелый алгоритм пересчета остатков ОбновитьОстаткиНаСервере. Зачем? Чтобы показать актуальные остатки на форме. Но ведь форма через долю секунды будет закрыта! Пользователь никогда не увидит эти рассчитанные данные. В масштабах крупного предприятия с сотнями одновременно работающих менеджеров такие «холостые» расчеты создают колоссальную паразитную нагрузку на СУБД и сервер приложений 1С.

К сожалению, в событии ПослеЗаписиНаСервере платформа не предоставляет нам штатного флага вроде ФормаЗакрывается. Тем не менее, существует три надежных архитектурных способа решения этой проблемы.

Вариант А. Переопределение стандартных команд (Собственная кнопка)

Этот способ является наиболее строгим и надежным с точки зрения логики работы интерфейса. Мы скрываем стандартные командные кнопки формы «Провести и закрыть» и создаем вместо них собственную команду.

Рассмотрим реализацию этого алгоритма:


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

Далее мы перехватываем управление в предопределенном клиентском событии ПослеЗаписи:


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

Вариант Б. Использование однократного обработчика ожидания

Если вы не хотите переопределять стандартные кнопки формы (например, чтобы сохранить типовой интерфейс системы без лишней доработки командных панелей в расширении), можно применить красивый асинхронный паттерн с использованием **обработчика ожидания**.

Суть метода заключается в следующем: в событии ПослеЗаписи на клиенте мы не вызываем сервер немедленно. Вместо этого мы подключаем однократный обработчик ожидания с минимальной задержкой (например, 0.1 секунды).


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    
    // Подключаем однократный обработчик ожидания. 
    // Третий параметр "Истина" указывает на то, что обработчик выполнится только один раз.
    ПодключитьОбработчикОжидания("Подключаемый_ВыполнитьПересчетОстатков", 0.1, Истина);
    
КонецПроцедуры

&НаКлиенте
Процедура Подключаемый_ВыполнитьПересчетОстатков()
    
    // Внимание! Если пользователь нажал "Провести и закрыть", то за 0.1 секунды 
    // платформа успеет закрыть форму. Контекст формы уничтожится, 
    // и этот обработчик ожидания просто никогда не выполнится. 
    // Сервер не получит лишнего вызова!
    // Если же форма осталась открытой — мы спокойно выполняем пересчет остатков.
    
    ОбновитьОстаткиНаСервере();
    
КонецПроцедуры

Этот способ является крайне элегантным, не требует изменения состава кнопок формы и отлично решает проблему избыточной нагрузки на сервер.

Вариант В. Асинхронный сдвиг (для современных версий платформы)

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

Часть 3. Почему в расширении скрыты события таблицы формы и как это исправить

Перейдем к третьему, чисто практическому вопросу, вызывающему массу недоумений у начинающих разработчиков расширений.

Сценарий: вы добавили форму документа в расширение. На форме есть таблица (например, Товары). Вы открываете палитру свойств этой таблицы в Конфигураторе и видите, что список доступных событий пуст или содержит всего 2-3 пункта, хотя в основной конфигурации у этой таблицы доступны десятки событий (ПриАктивизацииСтроки, ПередНачаломДобавления, ПриИзменении и др.). При этом, если прописать обработчик вручную в коде модуля, он прекрасно компилируется и срабатывает.

Выясним причину этого поведения конфигуратора. Управляемая форма в 1С жестко завязана на **типизацию данных**. Таблица формы визуализирует данные определенного типа (например, табличную часть документа ДокументТабличнаяЧасть.ЗаказКлиента.Товары). Когда вы добавляете форму в расширение, Конфигуратор видит визуальный элемент «Таблица», но он не имеет информации о типе данных, который лежит в основе этой таблицы, потому что сам объект метаданных (Документ) или его табличная часть еще не добавлены в расширение!

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

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

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

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

Мы детально изучили нюансы клиент-серверного взаимодействия в управляемых формах 1С. Применение рассмотренных методик позволит вам создавать производительные решения, бережно расходующие ресурсы серверов и СУБД, а также избегать досадных трудностей при проектировании в механизме расширений.

← На главную