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

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

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

Детальный разбор асинхронного подхода (решение, рекомендованное на форуме)

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

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

  1. Открытие формы без модальности: Вместо традиционного модального вызова ПолучитьФорму().ОткрытьМодально(), который блокирует весь поток выполнения кода до закрытия формы, мы используем метод ОткрытьФорму(). Этот метод является асинхронным; он не блокирует выполнение кода и сразу возвращает управление вызывающей процедуре. Это фундаментальный принцип современных управляемых форм.
  2. Использование ОписаниеОповещения: Чтобы получить результат после закрытия открытой формы, нам нужно "сообщить" платформе, какую именно процедуру следует вызвать в нашей текущей (вызывающей) форме. Для этого предназначен специальный объект ОписаниеОповещения. При его создании мы указываем два ключевых параметра: имя процедуры-обработчика, которая будет вызвана после закрытия формы, и контекст (обычно ЭтотОбъект), в котором эта процедура должна быть вызвана.
  3. Блокировка окна владельца (эмуляция модальности для пользователя): Хотя мы отказались от программной модальности, для пользователя бывает привычнее, когда дочернее окно требует внимания и блокирует взаимодействие с родительским. Мы можем создать эффект, схожий с модальным окном, блокируя только родительскую форму, но не останавливая выполнение программного кода. Это достигается путем передачи параметра РежимОткрытияОкнаФормы.БлокироватьОкноВладельца в метод ОткрытьФорму(). Пользователь не сможет взаимодействовать с родительской формой до тех пор, пока не закроет дочернюю, но наш программный код будет продолжать работать, ожидая срабатывания ОписаниеОповещения.
  4. Возврат значения из дочерней формы: Самый критически важный момент — в открываемой форме (в нашем случае, форме элемента справочника) мы должны явно указать, какое значение следует вернуть. Это делается с помощью метода Закрыть(), который вызывается в обработчике события, например, ПередЗакрытием формы. В качестве параметра метода Закрыть() мы передаем ту информацию, которую хотим получить обратно, в нашем случае – ссылку на созданный объект. Платформа 1С автоматически передаст это значение в процедуру-обработчик, указанную в ОписаниеОповещения.

Посмотрим на пример реализации, основанный на решении, представленном в теме форума:

Код в исходной форме (той, откуда мы открываем форму для создания элемента справочника):


&НаКлиенте
Процедура СоздаемНовыйОбъект()
    // 1. Создаем ОписаниеОповещения.
    // "ПолучитьСсылкуОбъекта" - это имя процедуры-обработчика, которая будет вызвана в текущей форме.
    // "ЭтотОбъект" - это контекст, в котором будет вызвана процедура (то есть, текущая форма).
    ОписаниеОповещения = Новый ОписаниеОповещения("ПолучитьСсылкуОбъекта", ЭтотОбъект);
    
    // 2. Открываем форму элемента справочника "Номенклатура".
    // Мы передаем имя формы, затем оставляем параметры открытия пустыми, указываем владельца формы (ЭтаФорма),
    // оставляем уникальность и окно пустыми.
    // 3. В качестве шестого параметра передаем наше ОписаниеОповещения.
    // 4. Седьмой параметр (РежимОткрытияОкнаФормы.БлокироватьОкноВладельца) обеспечивает блокировку родительского окна,
    // что для пользователя выглядит как модальное окно, но не останавливает выполнение кода.
    ОткрытьФорму("Справочник.Номенклатура.ФормаОбъекта", , ЭтаФорма, , , ОписаниеОповещения, РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
КонецПроцедуры;

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

Код в модуле формы элемента справочника (например, "Справочник.Номенклатура.ФормаОбъекта"):


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

Важно отметить, что метод Закрыть() можно вызывать не только из события ПередЗакрытием, но и из других событий, например, после успешной записи объекта (ПослеЗаписиНаСервере, с последующим вызовом Закрыть() на клиенте). Однако обработчик ПередЗакрытием является наиболее универсальным местом для передачи ссылки, так как он срабатывает при любом закрытии формы, позволяя гибко обрабатывать как сохранение, так и отмену.

Альтернативный метод: Предварительная установка ссылки

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

Проанализируем алгоритм действий при использовании этого метода:

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

Посмотрим на пример реализации:

Код в исходной форме (откуда мы открываем форму для создания элемента):


&НаКлиенте
Перем мПредопределеннаяСсылкаДляНоменклатуры; // Переменная для сохранения ссылки

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

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

&НаСервере
Функция ПолучитьСсылкаЭлементаНаСервере(Ссылка)
    // Серверная функция для проверки существования объекта по ссылке.
    // Если объект существует, возвращаем ссылку, иначе - Неопределено.
    Если Ссылка.Метаданные() <> Неопределено Тогда // Проверяем, что ссылка валидна
        Возврат Ссылка.ПолучитьОбъект() <> Неопределено ? Ссылка : Неопределено;
    Иначе
        Возврат Неопределено;
    КонецФункции;

Код в модуле формы элемента справочника (например, "Справочник.Номенклатура.ФормаОбъекта"):


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Этот обработчик события "ПриСозданииНаСервере" срабатывает, когда на сервере создается новый объект,
    // связанный с формой, но еще до того, как форма будет полностью инициализирована.
    
    // 3. Проверяем, были ли переданы параметры, и содержит ли параметр "Ключ" наше свойство "ПредопределеннаяСсылка".
    Если Параметры.Свойство("Ключ") И Параметры.Ключ.Свойство("ПредопределеннаяСсылка") Тогда
        // Если да, то мы устанавливаем эту предопределенную ссылку для нового объекта.
        // Это гарантирует, что при сохранении объекта в базе данных он получит именно эту ссылку.
        Объект.УстановитьСсылкуНового(Параметры.Ключ.ПредопределеннаяСсылка);
    КонецЕсли;
КонецПроцедуры;

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

Другой подход: Глобальное оповещение

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

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

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

Посмотрим на пример реализации:

Код в модуле формы элемента справочника (например, "Справочник.Номенклатура.ФормаОбъекта"), для события "ПослеЗаписиНаСервере":


&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
    // Этот обработчик вызывается после того, как объект (элемент справочника) был успешно записан на сервере.
    // Мы можем добавить проверку, чтобы оповещать только при создании нового объекта:
    Если ТекущийОбъект.ЭтоНовый() Тогда
        // 1. Вызываем глобальный метод Оповестить().
        // Первый параметр - это уникальное имя события, которое мы сами придумываем, например, "НовыйЭлементНоменклатурыСоздан".
        // Второй параметр - это данные, которые мы хотим передать; в нашем случае, ссылка на только что созданный объект.
        // Третий параметр - источник оповещения, обычно текущая форма (ЭтаФорма).
        Оповестить("НовыйЭлементНоменклатурыСоздан", ТекущийОбъект.Ссылка, ЭтаФорма);
    КонецЕсли;
КонецПроцедуры;

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


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

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

Самый современный синтаксис: Async/Await

Начиная с версии платформы 8.3.18, в 1С:Предприятии появился новый синтаксис для работы с асинхронными вызовами – Async/Await (в русскоязычной локализации – Асинх/Ждать). Этот синтаксис является "синтаксическим сахаром" над механизмом ОписаниеОповещения и значительно упрощает чтение и написание кода, делая асинхронные операции более линейными и понятными, похожими на синхронные вызовы, но без блокировки интерфейса.

Давайте посмотрим, как это работает и в чем его преимущества:

  1. Объявление асинхронной процедуры: Процедура, которая будет выполнять асинхронный вызов, должна быть объявлена с ключевым словом Асинх (или Async) перед словом Процедура. Это указывает платформе, что данная процедура может содержать асинхронные операции.
  2. Ожидание асинхронной функции: Для вызова асинхронной функции (например, ОткрытьФормуАсинх()), мы используем ключевое слово Ждать (или Await). Оператор Ждать "ставит на паузу" выполнение текущей асинхронной процедуры до тех пор, пока вызванная асинхронная операция не завершится и не вернет результат. После завершения операции выполнение процедуры возобновляется с того места, где было вызвано Ждать, и полученный результат присваивается переменной.
  3. Требование к возврату значения из дочерней формы: Важно понимать, что для того чтобы ОткрытьФормуАсинх() вернула результат (в нашем случае, ссылку на созданный элемент), в открываемой форме элемента справочника по-прежнему необходимо в обработчике события, например, ПередЗакрытием, вызывать Закрыть(Объект.Ссылка). Это точно такой же подход, который мы использовали в первом, основном методе с ОписаниеОповещения. Синтаксис Async/Await упрощает *вызов* и *ожидание* результата, но не меняет *механизм возврата* значения из закрываемой формы.

Пример реализации с Async/Await:

Код в исходной форме (откуда мы открываем форму для создания элемента):


&НаКлиенте
Асинх Процедура СоздаемНовыйОбъект_AsyncAwait()
    // 1. Объявляем процедуру как асинхронную, используя ключевое слово "Асинх".
    
    // 2. Используем оператор "Ждать" перед вызовом асинхронной функции ОткрытьФормуАсинх().
    // Выполнение кода в этой процедуре будет приостановлено до закрытия открытой формы.
    // После закрытия формы, результат (переданный через Закрыть() в дочерней форме)
    // будет присвоен переменной Результат, и выполнение продолжится.
    Результат = Ждать ОткрытьФормуАсинх("Справочник.Номенклатура.ФормаОбъекта", , , , , , РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
    
    // После получения результата мы можем с ним работать так же, как с обычным значением.
    Если Результат <> Неопределено И ЗначениеЗаполнено(Результат) Тогда
        // Результат - это и есть ссылка на созданный элемент, переданная из закрывающейся формы.
        Сообщить("Создан новый элемент (Async/Await): " + Результат);
        // Здесь мы можем выполнять дальнейшую обработку полученной ссылки.
    Иначе
        Сообщить("Создание нового элемента отменено или форма закрыта без сохранения (Async/Await).");
    КонецЕсли;

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

Код в модуле формы элемента справочника (например, "Справочник.Номенклатура.ФормаОбъекта") остается тем же, что и для асинхронного подхода с ОписаниеОповещения:


&НаКлиенте
Процедура ПередЗакрытием(Отказ, СтандартнаяОбработка)
    // Этот код остается без изменений.
    // Он обеспечивает передачу ссылки обратно в вызывающую форму,
    // независимо от того, был ли вызов через ОписаниеОповещения или через Async/Await.
    Закрыть(Объект.Ссылка);
КонецПроцедуры;

Использование Async/Await делает код значительно более лаконичным и интуитивно понятным, избавляя нас от необходимости явно создавать ОписаниеОповещения и отдельные процедуры обратного вызова. Это способствует написанию более читаемого и легко поддерживаемого кода, при этом сохраняя все преимущества асинхронной работы и не блокируя пользовательский интерфейс. Этот подход является наиболее современным и предпочтительным для новых разработок.

Вывод:

Мы рассмотрели несколько подходов для получения ссылки на созданный элемент справочника после открытия его формы в 1С:Предприятии. Наиболее современным и рекомендованным является асинхронный подход с использованием ОткрытьФорму() в связке с ОписаниеОповещения или с применением синтаксиса Async/Await (для платформ 8.3.18 и выше). Эти методы позволяют нам создавать гибкие, отзывчивые и неблокирующие интерфейсы, соответствующие современным требованиям к пользовательскому опыту и корректной работе в различных клиентах 1С:Предприятия. Альтернативные методы, такие как предварительная установка ссылки или глобальное оповещение, могут быть полезны в специфических сценариях, требующих минимальных изменений в типовой конфигурации или особой логики работы с событиями. Выбор конкретного подхода зависит от требований к гибкости, поддержки конфигурации и используемой версии платформы.

← На главную