При разработке расширений или доработке конфигураций на платформе 1С:Предприятие 8.3 часто возникает задача создания специализированных форм подбора. Например, мы хотим открыть из документа «Реализация товаров и услуг» свою особую форму, найти или создать там номенклатуру, а затем добавить эту позицию в табличную часть исходного документа.
Многие начинающие разработчики сталкиваются с проблемой: при попытке обратиться к документу из новой формы через ЭтотОбъект или реквизит с типом ДокументОбъект, они получают пустой или новый документ, а не тот, который открыт на экране. Давайте разберем, почему так происходит, и как реализовать этот механизм правильно.
Рассмотрим типичную ошибку. Разработчик создает новую форму, добавляет туда реквизит Объект и пытается программно обратиться к нему, чтобы добавить строку в товары.
В управляемом приложении каждая форма работает в своем изолированном контексте. Когда вы открываете новую форму (даже если она принадлежит тому же типу документа), платформа создает новый экземпляр данных в памяти. Этот экземпляр ничего не знает о той форме, из которой он был вызван.
Как метко заметили в обсуждении, это похоже на поход в магазин:
Вы вышли из дома (форма документа) и пошли в магазин (форма подбора). Когда вы купили товар, вы не идете строить новый дом, чтобы положить туда покупки. Вы возвращаетесь в свой старый дом с товаром.
Попытка использовать РеквизитФормыВЗначение("Объект") во второй форме создает в памяти копию объекта базы данных (или совершенно новый объект), который никак не связан с тем документом, который видит пользователь на экране в первой форме.
Чтобы добавить строку в документ, мы не должны пытаться «залезть» в него из внешней формы. Вместо этого мы должны вернуть найденную номенклатуру из формы подбора, а саму вставку строки произвести уже в модуле формы документа.
Давайте реализуем это пошагово.
В форме документа «Реализация товаров и услуг» (в команде вашей кнопки) мы будем использовать конструктор ОписаниеОповещения. Это инструкция для платформы: «какую процедуру выполнить, когда вспомогательная форма закроется».
&НаКлиенте
Процедура ПодобратьТовар(Команда)
// Создаем описание оповещения.
// "ОбработатьВыборТовара" - это имя процедуры в этой же форме, которая сработает после закрытия окна подбора.
Оповещение = Новый ОписаниеОповещения("ОбработатьВыборТовара", ЭтотОбъект);
// Открываем нашу форму из расширения.
// Параметры можно передать структурой (если нужно), владелец - ЭтаФорма.
ОткрытьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаПодбораСпец", , ЭтаФорма, , , , Оповещение);
КонецПроцедуры
Теперь перейдем в нашу произвольную форму (в расширении), где пользователь выбирает или создает номенклатуру. Допустим, у нас есть кнопка «Перенести в документ».
В обработчике этой кнопки мы не пишем код добавления строк. Мы просто закрываем форму и передаем в метод Закрыть() нужные данные. Обычно передают структуру.
&НаКлиенте
Процедура ПеренестиВДокумент(Команда)
// Подготовим данные для передачи
ДанныеВыбора = Новый Структура;
ДанныеВыбора.Вставить("Номенклатура", Объект.Номенклатура); // Или реквизит формы, где хранится ссылка
ДанныеВыбора.Вставить("Количество", Объект.Количество);
ДанныеВыбора.Вставить("Цена", Объект.Цена);
// Закрываем форму и передаем данные "наверх"
Закрыть(ДанныеВыбора);
КонецПроцедуры
Вернемся в модуль формы документа «Реализация товаров и услуг». Нам нужно описать ту самую экспортную процедуру, которую мы указали в ОписаниеОповещения.
&НаКлиенте
Процедура ОбработатьВыборТовара(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
// Проверяем, что результат не неопределен (пользователь мог просто закрыть окно крестиком)
Если РезультатЗакрытия = Неопределено Тогда
Возврат;
КонецЕсли;
// РезультатЗакрытия - это та самая структура, которую мы передали в методе Закрыть()
// Добавляем строку в табличную часть.
// Обратите внимание: Объект.Товары доступен на клиенте для чтения и добавления строк!
НоваяСтрока = Объект.Товары.Добавить();
НоваяСтрока.Номенклатура = РезультатЗакрытия.Номенклатура;
НоваяСтрока.Количество = РезультатЗакрытия.Количество;
НоваяСтрока.Цена = РезультатЗакрытия.Цена;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
// Если нужно пересчитать суммы документа, можно вызвать серверный метод
// РассчитатьСуммуДокументаНаСервере();
КонецПроцедуры
Существует еще один способ, который часто используют, но он считается менее «чистым» с точки зрения архитектуры, так как создает жесткую связность между формами. Однако в простых случаях он работает.
При открытии формы мы указали владельца: ЭтаФорма. В открытой форме мы можем обратиться к этому владельцу через свойство ЭтаФорма.Владелец.
Пример кода во второй форме (форме подбора), если мы хотим сразу писать в родитель:
&НаКлиенте
Процедура ПеренестиВДокументНапрямую(Команда)
// Получаем форму-владельца
ФормаВладелец = ЭтаФорма.Владелец;
// Проверяем, что владелец существует и у него есть Объект
Если ФормаВладелец <> Неопределено И ТипЗнч(ФормаВладелец) = Тип("УправляемаяФорма") Then
// Обращаемся к данным владельца
// ВАЖНО: Это сработает, только если данные доступны на клиенте
НоваяСтрока = ФормаВладелец.Объект.Товары.Добавить();
НоваяСтрока.Номенклатура = Объект.Номенклатура;
// Оповещаем владельца об изменении данных (чтобы загорелась звездочка модифицированности)
ФормаВладелец.Модифицированность = Истина;
Закрыть();
КонецЕсли;
КонецПроцедуры
Почему первый способ лучше?
ОписаниеОповещения соответствует стандартам разработки для веб-клиента и мобильных приложений 1С.Для решения задачи добавления данных в документ из другой формы используйте схему:
ОткрытьФорму с параметром ОписаниеОповещения.Закрыть(Структура).Объект.Товары.