Почему переменная модуля объекта 1С теряет значение между событиями ПриКопировании и ПриЗаписи?

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

Часто разработчики сталкиваются с ситуацией, когда данные, сохраненные в переменной модуля объекта в одном обработчике событий, оказываются недоступными в другом. Классический пример — попытка передать значение из обработчика ПриКопировании в обработчик ПриЗаписи. Несмотря на то, что рекомендации ИТС говорят о возможности использования переменных модуля для обмена данными между обработчиками, на практике такой код не работает. Давайте разберемся, почему так происходит и как правильно решать подобные задачи.

Проанализируем исходную проблему. Разработчик объявил в модуле объекта переменную:

Перем ОбъектКопированияСсылка;

Затем, в обработчике события копирования, он присвоил ей значение:


Процедура ПриКопировании(ОбъектКопирования)
    ОбъектКопированияСсылка = ОбъектКопирования.Ссылка;
КонецПроцедуры

А при записи нового, скопированного объекта, попытался это значение использовать:


Процедура ПриЗаписи(Отказ)
    Если ЗначениеЗаполнено(ОбъектКопированияСсылка) Тогда // Здесь переменная пустая
        // ... выполнить какие-то действия
    КонецЕсли;
КонецПроцедуры

К удивлению, в процедуре ПриЗаписи переменная ОбъектКопированияСсылка оказывается пустой. Выясним причину такого поведения.

Главная причина: жизненный цикл объекта на сервере

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

Рассмотрим по шагам, что происходит в нашей ситуации:

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

Именно поэтому передать данные таким способом невозможно. Это два разных экземпляра объекта, живущие в разное время.

Когда же работает рекомендация ИТС?

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

Посмотрим на этот пример:


Перем ПредыдущееЗначениеОрганизации;

Процедура ПередЗаписью(Отказ)
    // Получаем старое значение реквизита из базы данных
    ПредыдущееЗначениеОрганизации = ОбщегоНазначения.ПолучитьЗначениеРеквизита(Ссылка, "Организация");
КонецПроцедуры

Процедура ПриЗаписи(Отказ)
    // Сравниваем старое значение с новым
    Если ПредыдущееЗначениеОрганизации <> Организация Тогда
        // Выполняем действия при смене организации
        ...
    КонецЕсли;
КонецПроцедуры

Почему этот код работает? Потому что, когда пользователь нажимает "Записать", происходит один серверный вызов, в рамках которого:

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

Как правильно передавать данные между разными серверными вызовами?

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

Решение 1: Использование реквизитов формы

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

Шаг 1. Добавьте реквизит в форму объекта.

Откройте форму объекта и добавьте новый реквизит, например, ОбъектИсточникКопирования с типом "СправочникСсылка" или "ДокументСсылка" в зависимости от вашего объекта. Важно: этот реквизит не должен быть связан с реквизитом объекта, то есть не должен сохраняться в базу данных. Это просто временное хранилище на форме.

Шаг 2. Заполните реквизит формы в модуле объекта.

В модуле объекта, в обработчике ПриКопировании, мы можем обратиться к форме и заполнить наш новый реквизит. Это делается через параметр ОбъектКопирования, который на самом деле является объектом-формой.


&НаСервере
Процедура ПриКопировании(ОбъектКопирования)
    // ОбъектКопирования здесь - это не объект данных, а форма!
    // Поэтому мы обращаемся к ее реквизиту.
    ОбъектКопирования.ОбъектИсточникКопирования = ЭтотОбъект.Ссылка; 
КонецПроцедуры

Шаг 3. Получите данные при записи.

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


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

Этот способ надежен, так как данные "путешествуют" вместе с формой с клиента на сервер и обратно.

Решение 2: Использование временного хранилища

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

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

Подведем итог

Основной вывод, который нужно сделать: переменные модуля объекта живут только в рамках одного серверного вызова.

Понимание этого принципа работы клиент-серверной архитектуры 1С поможет избежать множества ошибок и писать более надежный и предсказуемый код.

← На главную